Skip to content
Open
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
3,129 changes: 1,541 additions & 1,588 deletions Cargo.lock

Large diffs are not rendered by default.

89 changes: 46 additions & 43 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,33 +30,33 @@ sea-orm = { version = "1.1.8", features = [
serde = { version = "1.0.219", features = ["derive"] }
tokio = { version = "1.44.1", features = ["full"] }
toml = { version = "0.8.20" }
yellowstone-grpc-proto = { version = "9", default-features = false }
yellowstone-grpc-client = { version = "9" }
yellowstone-grpc-proto = { git = "https://github.com/rpcpool/yellowstone-grpc", tag = "v16.0.0-rc3+solana.4.3.0.beta.2", default-features = false }
yellowstone-grpc-client = { git = "https://github.com/rpcpool/yellowstone-grpc", tag = "v16.0.0-rc3+solana.4.3.0.beta.2" }
anyhow = "1.0.97"
thiserror = "2.0.12"
async-trait = "0.1.88"
solana-account = "4.3.1"
solana-pubkey = "4.2.0"
solana-program = "4.0.0"
solana-runtime = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-accounts-db = { version = "4.2.1", features = ["agave-unstable-api"] }
agave-fs = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-hash = "4.4.0"
solana-hard-forks = "3.1.1"
solana-fee-calculator = "3.2.2"
solana-account = "4.5.0"
solana-pubkey = "4.3.0"
solana-program = "4.1.0"
solana-runtime = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-accounts-db = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
agave-fs = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-hash = "4.6.0"
solana-hard-forks = "3.2.0"
solana-fee-calculator = "3.3.0"
solana-rent-collector = "2.3.0"
solana-epoch-schedule = "3.2.0"
solana-inflation = "3.1.1"
solana-stake-interface = "4.3.0"
solana-epoch-schedule = "3.3.0"
solana-inflation = "3.2.0"
solana-stake-interface = "4.4.0"
solana-serde = "3.0.0"
solana-rpc-client-api = "4.2.1"
solana-account-decoder-client-types = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-account-decoder = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-vote-interface = { version = "6.0.2", default-features = false, features = ["bincode", "serde"] }
solana-vote = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-rpc-client-api = "=4.3.0-beta.2"
solana-account-decoder-client-types = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-account-decoder = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-vote-interface = { version = "6.1.0", default-features = false, features = ["bincode", "serde"] }
solana-vote = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-commitment-config = "3.1.1"
solana-client = "4.2.1"
solana-nonce-account = "4.1.0"
solana-client = "=4.3.0-beta.2"
solana-nonce-account = "4.3.0"
futures = "0.3.31"
rust_decimal = "1.37.1"
pin-project = "1.1.10"
Expand All @@ -69,31 +69,34 @@ prometheus = { version = "0.14.0", features = ["push"] }
reqwest = "0.12"
tower = "0.5.2"
bs58 = "0.5.1"
solana-lattice-hash = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-svm = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-svm-callback = { version = "=4.2.1", features = ["agave-unstable-api"] }
agave-precompiles = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-program-runtime = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-syscalls = { version = "4.2.1", default-features = false, features = ["agave-unstable-api"] }
solana-builtins = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-bpf-loader-program = { version = "4.2.1", features = ["agave-unstable-api"] }
agave-feature-set = { version = "=4.2.1", features = ["agave-unstable-api"] }
agave-reserved-account-keys = { version = "=4.2.1", features = ["agave-unstable-api"] }
solana-compute-budget = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-compute-budget-instruction = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-svm-feature-set = "=4.2.1"
solana-svm-transaction = "=4.2.0"
solana-transaction = { version = "4.1.4", features = ["blake3", "verify", "serde"] }
solana-message = "4.2.3"
solana-address-lookup-table-interface = { version = "3.1", features = ["bincode", "bytemuck"] }
solana-lattice-hash = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-svm = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-svm-callback = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
agave-precompiles = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-program-runtime = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-syscalls = { version = "=4.3.0-beta.2", default-features = false, features = ["agave-unstable-api"] }
solana-builtins = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-bpf-loader-program = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
agave-feature-set = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
agave-reserved-account-keys = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-compute-budget = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-compute-budget-instruction = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-svm-feature-set = "=4.3.0-beta.2"
solana-svm-transaction = "5.0.0"
solana-runtime-transaction = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-fee = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
solana-packet = "4.3.0"
solana-transaction = { version = "4.2.0", features = ["blake3", "verify", "serde"] }
solana-message = "4.5.0"
solana-address-lookup-table-interface = { version = "3.2", features = ["bincode", "bytemuck"] }
solana-loader-v3-interface = { version = "7.0", features = ["serde"] }
solana-sdk-ids = "3.1"
solana-clock = "3.0.1"
solana-fee-structure = "3.0"
solana-rent = "4.2.1"
solana-transaction-error = "3.3.1"
solana-clock = "3.2.0"
solana-fee-structure = "4.1"
solana-rent = "4.4.0"
solana-transaction-error = "3.4.0"
solana-precompile-error = "3.0"
solana-transaction-status-client-types = { version = "4.2.1", features = ["agave-unstable-api"] }
solana-transaction-status-client-types = { version = "=4.3.0-beta.2", features = ["agave-unstable-api"] }
spl-token-2022 = "11.0.0"
spl-token-2022-interface = "3.1.0"
spl-token-interface = "3.0.0"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,8 @@ The recomputer detects drift by comparing the total activated stake across runs.

The transaction is executed read-only against the indexed account state at the requested slot; nothing is committed. Cloudbreak reconstructs the cluster's actually-activated feature set at that slot from the on-chain feature accounts (rather than enabling all features), so compute-unit accounting and execution behaviour match mainnet. `replaceRecentBlockhash` substitutes the latest recorded blockhash before execution and reports it with its `lastValidBlockHeight`; `sigVerify` verifies signatures; `accounts` returns post-simulation state for the requested addresses; `innerInstructions` includes decoded inner instructions.

Cloudbreak accepts legacy, v0, and v1 transactions. A v1 transaction can be larger than the legacy 1232-byte packet, so the decoder applies the version's own size bound and rejects anything larger with an invalid-params error.

### Supply (`getSupply`)

`getSupply` is optional. It needs a **full, unfiltered index** (empty `[programs]` include and exclude lists), `accounts-owner-map-enabled = false`, owner partitioning off (single `accounts` and `snapshot_accounts` tables), and the `[snapshot]` section. Enable the `[supply]` section on the indexer and the `[supply]` section on the API. The two must be set consistently; a node with the API section absent or disabled answers `getSupply` with method-not-found. Run it on a dedicated supply-only instance: de-partitioning removes the owner pruning that `getProgramAccounts` relies on.
Expand Down
3 changes: 3 additions & 0 deletions crates/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ solana-compute-budget = { workspace = true }
solana-compute-budget-instruction = { workspace = true }
solana-svm-feature-set = { workspace = true }
solana-svm-transaction = { workspace = true }
solana-runtime-transaction = { workspace = true }
solana-fee = { workspace = true }
solana-packet = { workspace = true }
solana-transaction = { workspace = true }
solana-message = { workspace = true }
solana-address-lookup-table-interface = { workspace = true }
Expand Down
133 changes: 100 additions & 33 deletions crates/api/src/methods/simulate_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,23 @@ use solana_account_decoder_client_types::{UiAccount, token::UiTokenAmount};
use solana_address_lookup_table_interface::state::AddressLookupTable;
use solana_clock::{Epoch, Slot};
use solana_commitment_config::CommitmentLevel;
use solana_compute_budget_instruction::instructions_processor::process_compute_budget_instructions;
use solana_fee_structure::FeeDetails;
use solana_fee::{FeeFeatures, calculate_fee_details};
use solana_hash::Hash;
use solana_loader_v3_interface::state::UpgradeableLoaderState;
use solana_message::v0::{LoadedAddresses, MessageAddressTableLookup};
use solana_message::{AddressLoader, VersionedMessage};
use solana_precompile_error::PrecompileError;
use solana_program_runtime::execution_budget::{
SVMTransactionExecutionBudget, SVMTransactionExecutionCost,
SVMTransactionExecutionAndFeeBudgetLimits, SVMTransactionExecutionBudget,
SVMTransactionExecutionCost,
};
use solana_program_runtime::loaded_programs::{
BlockRelation, ForkGraph, ProgramRuntimeEnvironments,
};
use solana_program_runtime::program_cache_entry::ProgramCacheEntry;
use solana_pubkey::Pubkey;
use solana_rpc_client_api::config::RpcSimulateTransactionConfig;
use solana_runtime_transaction::transaction_meta::TransactionConfiguration;
use solana_rpc_client_api::response::{
Response as RpcResponse, RpcBlockhash, RpcResponseContext, RpcSimulateTransactionResult,
};
Expand All @@ -41,8 +42,9 @@ use solana_svm::transaction_processor::{
TransactionProcessingConfig, TransactionProcessingEnvironment,
};
use solana_svm_callback::{InvokeContextCallback, TransactionProcessingCallback};
use solana_svm_transaction::svm_message::SVMStaticMessage;
use solana_nonce_account::verify_nonce_account;
use solana_packet::PACKET_DATA_SIZE;
use solana_svm_transaction::svm_message::{SVMMessage, SVMStaticMessage};
use solana_transaction::sanitized::{MessageHash, SanitizedTransaction};
use solana_transaction::versioned::VersionedTransaction;
use solana_transaction_error::{AddressLoaderError, TransactionError};
Expand Down Expand Up @@ -220,11 +222,11 @@ pub async fn simulate_transaction(

let bank = SimulationBank::new(fetched, feature_set.clone());

let compute_budget_limits = match process_compute_budget_instructions(
sanitized_tx.program_instructions_iter(),
let transaction_configuration = match TransactionConfiguration::try_from_sanitized_message(
sanitized_tx.message(),
&feature_set,
) {
Ok(limits) => limits,
Ok(configuration) => configuration,
Err(e) => {
return Ok(response(
slot,
Expand All @@ -233,18 +235,23 @@ pub async fn simulate_transaction(
}
};

let signature_fee = sanitized_tx
.num_transaction_signatures()
.saturating_mul(blockhash_lamports_per_signature);
let fee_details = FeeDetails::new(
signature_fee,
compute_budget_limits.get_prioritization_fee(),
let fee_details = calculate_fee_details(
&sanitized_tx,
blockhash_lamports_per_signature,
transaction_configuration.priority_fee_lamports,
FeeFeatures {},
);
let budget_and_limits = compute_budget_limits.get_compute_budget_and_limits(
compute_budget_limits.loaded_accounts_bytes,
let budget_and_limits = SVMTransactionExecutionAndFeeBudgetLimits {
budget: SVMTransactionExecutionBudget {
compute_unit_limit: u64::from(transaction_configuration.compute_unit_limit),
heap_size: transaction_configuration.updated_heap_bytes,
..SVMTransactionExecutionBudget::new_with_defaults(
feature_set.is_active(&agave_feature_set::raise_cpi_nesting_limit_to_8::id()),
)
},
loaded_accounts_data_size_limit: transaction_configuration.loaded_accounts_data_size_limit,
fee_details,
feature_set.is_active(&agave_feature_set::raise_cpi_nesting_limit_to_8::id()),
);
};

let check_result: TransactionCheckResult = if runnable {
Ok(CheckedTransactionDetails::new(
Expand Down Expand Up @@ -428,28 +435,76 @@ fn resolve_nonce(
accounts: &HashMap<Pubkey, (AccountSharedData, Slot)>,
) -> Option<(Pubkey, u64)> {
let message = sanitized_tx.message();
let nonce_address = message.get_durable_nonce()?;
let nonce_address = SVMMessage::get_durable_nonce(message)?;
let (account, _) = accounts.get(nonce_address)?;
let data = verify_nonce_account(account, message.recent_blockhash())?;
message
.get_ix_signers(0)
SVMStaticMessage::get_ix_signers(message, 0)
.any(|signer| signer.to_bytes() == data.authority.to_bytes())
.then_some((*nonce_address, data.fee_calculator.lamports_per_signature))
}

const MAX_BASE58_SIZE: usize = 1683;
const MAX_BASE64_SIZE: usize = 5464;
const MAX_BASE64_LEGACY_SIZE: usize = 1644;
const V1_BASE64_PREFIX_LOWER_BOUND: &[u8] = b"gQ";

/// Decode the wire transaction per the requested encoding (base58 or base64).
fn decode_transaction(data: &str, encoding: UiTransactionEncoding) -> Result<Vec<u8>, RpcError> {
match encoding {
UiTransactionEncoding::Base58 | UiTransactionEncoding::Binary => bs58::decode(data)
.into_vec()
.map_err(|e| RpcError::InvalidParamsWithMessage(format!("invalid base58: {e}"))),
UiTransactionEncoding::Base64 => base64::prelude::BASE64_STANDARD
.decode(data)
.map_err(|e| RpcError::InvalidParamsWithMessage(format!("invalid base64: {e}"))),
other => Err(RpcError::InvalidParamsWithMessage(format!(
"unsupported transaction encoding: {other:?}"
))),
let (bytes, max_raw_size) = match encoding {
UiTransactionEncoding::Base58 | UiTransactionEncoding::Binary => {
if data.len() > MAX_BASE58_SIZE {
return Err(RpcError::InvalidParamsWithMessage(format!(
"base58 encoded {} too large: {} bytes (max: encoded/raw {}/{})",
std::any::type_name::<VersionedTransaction>(),
data.len(),
MAX_BASE58_SIZE,
PACKET_DATA_SIZE,
)));
}
let bytes = bs58::decode(data)
.into_vec()
.map_err(|e| RpcError::InvalidParamsWithMessage(format!("invalid base58: {e}")))?;
(bytes, PACKET_DATA_SIZE)
}
UiTransactionEncoding::Base64 => {
let (max_encoded_size, max_raw_size) = if data
.as_bytes()
.get(..2)
.is_some_and(|p| p >= V1_BASE64_PREFIX_LOWER_BOUND)
{
(MAX_BASE64_SIZE, solana_message::v1::MAX_TRANSACTION_SIZE)
} else {
(MAX_BASE64_LEGACY_SIZE, PACKET_DATA_SIZE)
};
if data.len() > max_encoded_size {
return Err(RpcError::InvalidParamsWithMessage(format!(
"base64 encoded {} too large: {} bytes (max: encoded/raw {}/{})",
std::any::type_name::<VersionedTransaction>(),
data.len(),
max_encoded_size,
max_raw_size,
)));
}
let bytes = base64::prelude::BASE64_STANDARD
.decode(data)
.map_err(|e| RpcError::InvalidParamsWithMessage(format!("invalid base64: {e}")))?;
(bytes, max_raw_size)
}
other => {
return Err(RpcError::InvalidParamsWithMessage(format!(
"unsupported transaction encoding: {other:?}"
)));
}
};
if bytes.len() > max_raw_size {
return Err(RpcError::InvalidParamsWithMessage(format!(
"decoded {} too large: {} bytes (max: {} bytes)",
std::any::type_name::<VersionedTransaction>(),
bytes.len(),
max_raw_size
)));
}
Ok(bytes)
}

fn response(
Expand Down Expand Up @@ -595,6 +650,18 @@ fn map_result(
None,
None,
),
Ok(ProcessedTransaction::NoOp(no_op)) => (
Some(UiTransactionError::from(TransactionError::clone(
&no_op.validation_error,
))),
Some(Vec::new()),
none_accounts,
Some(no_op.compute_unit_limit),
Some(no_op.loaded_accounts_bytes_limit),
None,
None,
None,
),
Err(e) => (
Some(UiTransactionError::from(e)),
Some(Vec::new()),
Expand Down Expand Up @@ -990,7 +1057,7 @@ fn execute(
for builtin in solana_builtins::BUILTINS {
processor.add_builtin(
builtin.program_id,
ProgramCacheEntry::new_builtin(0, builtin.name.len(), builtin.register_fn),
ProgramCacheEntry::new_builtin(0, builtin.register_fn),
);
}

Expand All @@ -1011,13 +1078,13 @@ fn execute(

let config = TransactionProcessingConfig {
account_overrides: None,
check_program_deployment_slot: false,
log_messages_bytes_limit: None,
limit_to_load_programs: true,
recording_config: ExecutionRecordingConfig::new_single_setting(true),
drop_on_failure: false,
all_or_nothing: false,
strict_nonce_size_check: false,
strict_nonce_size_check: true,
drop_noop_transactions: true,
};

Ok(processor.load_and_execute_sanitized_transactions(
Expand Down
5 changes: 4 additions & 1 deletion crates/index/src/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,10 @@ pub async fn process_update(
}
Some(UpdateOneof::Slot(slot_update)) => {
let slot = slot_update.slot;
let commitment = SlotStatus::try_from(slot_update.status).expect("Invalid slot status");
let Ok(commitment) = SlotStatus::try_from(slot_update.status) else {
tracing::error!("Unknown slot status {} for slot {}", slot_update.status, slot);
return;
};

match commitment {
SlotStatus::SlotProcessed | SlotStatus::SlotConfirmed => (),
Expand Down
1 change: 1 addition & 0 deletions crates/index/src/modules/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ pub fn subscribe_grpc_with_reconnection(
include_transactions: Some(false),
include_accounts: Some(true),
include_entries: Some(false),
cuckoo_account_include: None,
},
)]),
blocks_meta: HashMap::new(),
Expand Down
1 change: 1 addition & 0 deletions crates/snapshot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ async fn process_downloaded_snapshot(
}),
slot: account_file_slot,
is_startup: true,
bank_id: None,
};

current_accounts_chunk.push(account_update);
Expand Down