Skip to content

Commit 59aa231

Browse files
committed
core-bpf: special-case config program error codes
1 parent 1261e4b commit 59aa231

File tree

1 file changed

+66
-24
lines changed

1 file changed

+66
-24
lines changed

src/lib.rs

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -872,37 +872,79 @@ fn execute_instr(mut input: InstrContext) -> Option<InstrEffects> {
872872
let return_data = transaction_context.get_return_data().1.to_vec();
873873

874874
Some(InstrEffects {
875-
custom_err: if let Err(InstructionError::Custom(x)) = result {
876-
Some(x)
875+
custom_err: if let Err(InstructionError::Custom(code)) = result {
876+
#[cfg(feature = "core-bpf")]
877+
// See comment below under `result` for special-casing of custom
878+
// errors for Core BPF programs.
879+
if program_id == &solana_sdk::config::program::id() && (code == 0 || code == 1) {
880+
None
881+
} else {
882+
Some(code)
883+
}
884+
#[cfg(not(feature = "core-bpf"))]
885+
Some(code)
877886
} else {
878887
None
879888
},
880889
#[allow(clippy::map_identity)]
881890
result: result.err().map(|err| {
882891
#[cfg(feature = "core-bpf")]
883-
// Some errors don't directly map between builtins and their BPF
884-
// versions.
885-
//
886-
// For example, when a builtin program exceeds the compute budget,
887-
// the builtin's `DEFAULT_COMPUTE_UNITS` are deducted from the
888-
// meter, and if the meter is exhuasted, the invoke context will
889-
// throw `InstructionError::ComputationalBudgetExceeded`.
890-
// https://github.com/anza-xyz/agave/blob/6d74d13749829d463fabccebd8203edf0cf4c500/program-runtime/src/invoke_context.rs#L73
891-
// https://github.com/anza-xyz/agave/blob/6d74d13749829d463fabccebd8203edf0cf4c500/program-runtime/src/invoke_context.rs#L574
892-
//
893-
// However, for a BPF program, if the compute meter is exhausted,
894-
// the error comes from the VM, and is converted to
895-
// `InstructionError::ProgramFailedToComplete`.
896-
// https://github.com/solana-labs/rbpf/blob/69a52ec6a341bb7374d387173b5e6dc56218fe0c/src/error.rs#L44
897-
// https://github.com/anza-xyz/agave/blob/6d74d13749829d463fabccebd8203edf0cf4c500/program-runtime/src/invoke_context.rs#L547
898-
//
899-
// Therefore, some errors require reconciliation when testing a BPF
900-
// program against its builtin implementation.
901-
if err == InstructionError::ProgramFailedToComplete
902-
&& (input.cu_avail <= CORE_BPF_DEFAULT_COMPUTE_UNITS
903-
|| compute_units_consumed >= input.cu_avail)
904892
{
905-
return InstructionError::ComputationalBudgetExceeded;
893+
// Some errors don't directly map between builtins and their BPF
894+
// versions.
895+
//
896+
// For example, when a builtin program exceeds the compute budget,
897+
// the builtin's `DEFAULT_COMPUTE_UNITS` are deducted from the
898+
// meter, and if the meter is exhuasted, the invoke context will
899+
// throw `InstructionError::ComputationalBudgetExceeded`.
900+
// https://github.com/anza-xyz/agave/blob/6d74d13749829d463fabccebd8203edf0cf4c500/program-runtime/src/invoke_context.rs#L73
901+
// https://github.com/anza-xyz/agave/blob/6d74d13749829d463fabccebd8203edf0cf4c500/program-runtime/src/invoke_context.rs#L574
902+
//
903+
// However, for a BPF program, if the compute meter is exhausted,
904+
// the error comes from the VM, and is converted to
905+
// `InstructionError::ProgramFailedToComplete`.
906+
// https://github.com/solana-labs/rbpf/blob/69a52ec6a341bb7374d387173b5e6dc56218fe0c/src/error.rs#L44
907+
// https://github.com/anza-xyz/agave/blob/6d74d13749829d463fabccebd8203edf0cf4c500/program-runtime/src/invoke_context.rs#L547
908+
//
909+
// Therefore, some errors require reconciliation when testing a BPF
910+
// program against its builtin implementation.
911+
if err == InstructionError::ProgramFailedToComplete
912+
&& (input.cu_avail <= CORE_BPF_DEFAULT_COMPUTE_UNITS
913+
|| compute_units_consumed >= input.cu_avail)
914+
{
915+
return InstructionError::ComputationalBudgetExceeded;
916+
}
917+
// Another such error case arises when a program performs a write
918+
// to an account, but the data it writes is the exact same data
919+
// that's currently stored in the account state.
920+
//
921+
// For builtins, the `TransactionContext` is invoked when any write
922+
// is performed, asking it whether or not a write is allowed,
923+
// regardless of the data being written. If the account is not
924+
// writable, it throws `InstructionError::ReadonlyDataModified`.
925+
//
926+
// For BPF programs, writes to readonly accounts are caught _after_
927+
// the VM finishes execution, when the loader inspects the
928+
// serialized input data region. If a write was performed that did
929+
// not modify serialized account state, then no error is thrown.
930+
//
931+
// As a result, Core BPF programs have been outfitted with custom
932+
// errors when `is_writable` checks fail. These errors are
933+
// special-cased below to avoid fixture mismatches.
934+
match err {
935+
InstructionError::Custom(code) => {
936+
if program_id == &solana_sdk::config::program::id() {
937+
// Special-cased custom error codes for the Config program.
938+
if code == 0 {
939+
return InstructionError::ExecutableDataModified;
940+
}
941+
if code == 1 {
942+
return InstructionError::ReadonlyDataModified;
943+
}
944+
}
945+
}
946+
_ => {}
947+
}
906948
}
907949
err
908950
}),

0 commit comments

Comments
 (0)