Skip to content

Commit

Permalink
NDEV-3402 Interrupt transaction on Solana program call
Browse files Browse the repository at this point in the history
NDEV-3403 Implement finalization iteration right after Solana program call
  • Loading branch information
artem-yazkov committed Nov 5, 2024
1 parent de04f90 commit 1ec5339
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 18 deletions.
22 changes: 22 additions & 0 deletions evm_loader/program/src/account/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ struct Data {
pub priority_fee_used: U256,
/// Steps executed in the transaction
pub steps_executed: u64,
/// Steps interrupted due Solana program call
pub steps_interrupted: u64,
}

// Stores relative offsets for the corresponding objects as allocated by the AccountAllocator.
Expand Down Expand Up @@ -195,6 +197,7 @@ impl<'a> StateAccount<'a> {
gas_used: U256::ZERO,
priority_fee_used: U256::ZERO,
steps_executed: 0_u64,
steps_interrupted: 0_u64,
});

let data_offset = {
Expand Down Expand Up @@ -429,6 +432,25 @@ impl<'a> StateAccount<'a> {

Ok(())
}

#[must_use]
pub fn steps_interrupted(&self) -> u64 {
self.data.steps_interrupted
}

pub fn reset_steps_interrupted(&mut self) {
self.data.steps_interrupted = 0;
}

pub fn increment_steps_interrupted(&mut self, steps: u64) -> Result<()> {
self.data.steps_interrupted = self
.data
.steps_interrupted
.checked_add(steps)
.ok_or(Error::IntegerOverflow)?;

Ok(())
}
}

// Implementation of functional to save/restore persistent state of iterative transactions.
Expand Down
3 changes: 3 additions & 0 deletions evm_loader/program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,9 @@ pub enum Error {
#[error("Program not allowed to call itself")]
RecursiveCall,

#[error("Solana programs was interrupted")]
InterruptedCall,

#[error("External call fails {0}: {1}")]
ExternalCallFailed(Pubkey, String),

Expand Down
2 changes: 1 addition & 1 deletion evm_loader/program/src/evm/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ pub trait Database: LogCollector {

async fn precompile_extension(
&mut self,
context: &Context,
context: &mut Context,
address: &Address,
data: &[u8],
is_static: bool,
Expand Down
17 changes: 12 additions & 5 deletions evm_loader/program/src/evm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub enum ExitStatus {
Return(Vector<u8>),
Revert(Vector<u8>),
Suicide,
Interrupted,
StepLimit,
Cancel,
}
Expand All @@ -126,6 +127,7 @@ impl ExitStatus {
match self {
ExitStatus::Return(_) | ExitStatus::Stop | ExitStatus::Suicide => "succeed",
ExitStatus::Revert(_) => "revert",
ExitStatus::Interrupted => "interrupted due Solana call",
ExitStatus::StepLimit => "step limit exceeded",
ExitStatus::Cancel => "cancel",
}
Expand All @@ -135,7 +137,7 @@ impl ExitStatus {
pub fn is_succeed(&self) -> Option<bool> {
match self {
ExitStatus::Stop | ExitStatus::Return(_) | ExitStatus::Suicide => Some(true),
ExitStatus::Revert(_) | ExitStatus::Cancel => Some(false),
ExitStatus::Revert(_) | ExitStatus::Interrupted | ExitStatus::Cancel => Some(false),
ExitStatus::StepLimit => None,
}
}
Expand All @@ -144,9 +146,11 @@ impl ExitStatus {
pub fn into_result(self) -> Option<Vec<u8>> {
match self {
ExitStatus::Return(v) | ExitStatus::Revert(v) => Some(v.to_vec()),
ExitStatus::Stop | ExitStatus::Suicide | ExitStatus::StepLimit | ExitStatus::Cancel => {
None
}
ExitStatus::Stop
| ExitStatus::Suicide
| ExitStatus::Interrupted
| ExitStatus::StepLimit
| ExitStatus::Cancel => None,
}
}
}
Expand All @@ -165,8 +169,8 @@ pub struct Context {
pub contract: Address,
pub contract_chain_id: u64,
pub value: U256,

pub code_address: Option<Address>,
pub interrupt_solana_call: bool,
}

#[repr(C)]
Expand Down Expand Up @@ -275,6 +279,7 @@ impl<B: Database, T: EventListener> Machine<B, T> {
contract_chain_id: backend.contract_chain_id(target).await.unwrap_or(chain_id),
value: trx.value(),
code_address: Some(target),
interrupt_solana_call: true,
},
gas_price: trx.gas_price(),
gas_limit: trx.gas_limit(),
Expand Down Expand Up @@ -327,6 +332,7 @@ impl<B: Database, T: EventListener> Machine<B, T> {
contract_chain_id: chain_id,
value: trx.value(),
code_address: None,
interrupt_solana_call: true,
},
gas_price: trx.gas_price(),
gas_limit: trx.gas_limit(),
Expand Down Expand Up @@ -402,6 +408,7 @@ impl<B: Database, T: EventListener> Machine<B, T> {
Action::Return(value) => break ExitStatus::Return(value),
Action::Revert(value) => break ExitStatus::Revert(value),
Action::Suicide => break ExitStatus::Suicide,
Action::Interrupted => break ExitStatus::Interrupted,
Action::Noop => {}
};
}
Expand Down
19 changes: 14 additions & 5 deletions evm_loader/program/src/evm/opcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub enum Action {
Return(Vector<u8>),
Revert(Vector<u8>),
Suicide,
Interrupted,
Noop,
}

Expand Down Expand Up @@ -1110,6 +1111,7 @@ impl<B: Database, T: EventListener> Machine<B, T> {
contract_chain_id: chain_id,
value,
code_address: None,
interrupt_solana_call: true,
};

begin_vm!(self, backend, context, chain_id, init_code);
Expand Down Expand Up @@ -1164,6 +1166,7 @@ impl<B: Database, T: EventListener> Machine<B, T> {
contract_chain_id: backend.contract_chain_id(address).await.unwrap_or(chain_id),
value,
code_address: Some(address),
interrupt_solana_call: true,
};

begin_vm!(self, backend, context, chain_id, call_data);
Expand Down Expand Up @@ -1304,6 +1307,7 @@ impl<B: Database, T: EventListener> Machine<B, T> {
contract_chain_id: backend.contract_chain_id(address).await.unwrap_or(chain_id),
value: U256::ZERO,
code_address: Some(address),
interrupt_solana_call: true,
};

begin_vm!(self, backend, context, chain_id, call_data);
Expand Down Expand Up @@ -1337,16 +1341,21 @@ impl<B: Database, T: EventListener> Machine<B, T> {
Some(x) => Some(x),
None => {
backend
.precompile_extension(&self.context, address, &self.call_data, self.is_static)
.precompile_extension(
&mut self.context,
address,
&self.call_data,
self.is_static,
)
.await
}
};

if let Some(return_data) = result.transpose()? {
return self.opcode_return_impl(return_data, backend).await;
match result {
Some(Ok(return_data)) => self.opcode_return_impl(return_data, backend).await,
Some(Err(Error::InterruptedCall)) => Ok(Action::Interrupted),
_ => Ok(Action::Noop),
}

Ok(Action::Noop)
}

/// Halt execution returning output data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub async fn call_solana<State: Database>(
state: &mut State,
address: &Address,
input: &[u8],
context: &crate::evm::Context,
context: &mut crate::evm::Context,
is_static: bool,
) -> Result<Vector<u8>> {
if context.value != 0 {
Expand Down Expand Up @@ -309,14 +309,18 @@ pub async fn call_solana<State: Database>(
#[maybe_async]
async fn execute_external_instruction<State: Database>(
state: &mut State,
context: &crate::evm::Context,
context: &mut crate::evm::Context,
instruction: Instruction,
signer_seeds: Vector<Vector<u8>>,
required_lamports: u64,
) -> Result<Vector<u8>> {
#[cfg(not(target_os = "solana"))]
log::info!("instruction: {:?}", instruction);

if context.interrupt_solana_call {
context.interrupt_solana_call = false;
return Err(Error::InterruptedCall);
}
let called_program = instruction.program_id;
state.set_return_data(&[]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl PrecompiledContracts {
#[maybe_async]
pub async fn call_precompile_extension<State: Database>(
state: &mut State,
context: &Context,
context: &mut Context,
address: &Address,
input: &[u8],
is_static: bool,
Expand Down
2 changes: 1 addition & 1 deletion evm_loader/program/src/executor/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ impl<'a, B: AccountStorage> Database for ExecutorState<'a, B> {

async fn precompile_extension(
&mut self,
context: &Context,
context: &mut Context,
address: &Address,
data: &[u8],
is_static: bool,
Expand Down
2 changes: 1 addition & 1 deletion evm_loader/program/src/executor/synced_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ impl<'a, B: SyncedAccountStorage> Database for SyncedExecutorState<'a, B> {

async fn precompile_extension(
&mut self,
context: &Context,
context: &mut Context,
address: &Address,
data: &[u8],
is_static: bool,
Expand Down
62 changes: 60 additions & 2 deletions evm_loader/program/src/instruction/transaction_step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::debug::log_data;
use crate::error::{Error, Result};
use crate::evm::tracing::NoopEventListener;
use crate::evm::{ExitStatus, Machine};
use crate::executor::{Action, ExecutorState, ExecutorStateData};
use crate::executor::{Action, ExecutorState, ExecutorStateData, SyncedExecutorState};
use crate::gasometer::{Gasometer, LAMPORTS_PER_SIGNATURE};
use crate::instruction::priority_fee_txn_calculator;
use crate::types::boxx::boxx;
Expand Down Expand Up @@ -78,6 +78,58 @@ pub fn do_continue<'a>(
if reset {
log_data(&[b"RESET"]);
}
if storage.steps_interrupted() > 0 {
let chain_id = storage
.trx()
.chain_id()
.unwrap_or(crate::config::DEFAULT_CHAIN_ID);
let gas_limit = storage.trx().gas_limit();
let gas_price = storage.trx().gas_price();

storage
.trx()
.validate(storage.trx_origin(), &account_storage)?;
account_storage
.origin(storage.trx_origin(), &storage.trx())?
.increment_nonce()?;
let (exit_reason, steps_executed) = {
let mut backend = SyncedExecutorState::new(&mut account_storage);
let mut evm = Machine::new(
&storage.trx(),
storage.trx_origin(),
&mut backend,
None::<NoopEventListener>,
)?;
let (result, steps_executed, _) = evm.execute(u64::MAX, &mut backend)?;
(result, steps_executed)
};
log_data(&[
b"STEPS",
&steps_executed.to_le_bytes(), // Iteration steps
&steps_executed.to_le_bytes(), // Total steps is the same as iteration steps
]);
account_storage.increment_revision_for_modified_contracts()?;
account_storage.transfer_treasury_payment()?;

//gasometer.record_operator_expenses(account_storage.operator());
let used_gas = gasometer.used_gas();
if used_gas > gas_limit {
return Err(Error::OutOfGas(gas_limit, used_gas));
}
log_data(&[b"GAS", &used_gas.to_le_bytes(), &used_gas.to_le_bytes()]);

let gas_cost = used_gas.saturating_mul(gas_price);
let priority_fee =
priority_fee_txn_calculator::handle_priority_fee(&storage.trx(), used_gas)?;
account_storage.transfer_gas_payment(
storage.trx_origin(),
chain_id,
gas_cost + priority_fee,
)?;

log_return_value(&exit_reason);
return Ok(());
}

allocate_or_reinit_state(&mut account_storage, &mut storage, reset)?;
let mut state_data = storage.read_executor_state();
Expand Down Expand Up @@ -116,6 +168,7 @@ fn allocate_or_reinit_state(
) -> Result<()> {
if is_allocate {
storage.reset_steps_executed();
storage.reset_steps_interrupted();

// Dealloc objects that were potentially alloced in previous iterations before the reset.
if storage.is_evm_alloced() {
Expand Down Expand Up @@ -168,7 +221,11 @@ fn finalize<'a, 'b>(
}

let status = if let Some((status, actions)) = results {
if accounts.allocate(actions)? == AllocateResult::Ready {
if *status == ExitStatus::Interrupted {
accounts.apply_state_change(actions)?;
storage.increment_steps_interrupted(1)?;
None
} else if accounts.allocate(actions)? == AllocateResult::Ready {
accounts.apply_state_change(actions)?;
Some(status)
} else {
Expand Down Expand Up @@ -218,6 +275,7 @@ pub fn log_return_value(status: &ExitStatus) {
ExitStatus::Stop => 0x11,
ExitStatus::Return(_) => 0x12,
ExitStatus::Suicide => 0x13,
ExitStatus::Interrupted => 0x14,
ExitStatus::Revert(_) => 0xd0,
ExitStatus::StepLimit | ExitStatus::Cancel => unreachable!(),
};
Expand Down

0 comments on commit 1ec5339

Please sign in to comment.