zksync_node_api_server 0.1.0

ZKsync API server
use std::fmt;

use zksync_multivm::interface::{ExecutionResult, VmExecutionResultAndLogs};
use zksync_types::{
    fee::TransactionExecutionMetrics, l2::L2Tx, ExecuteTransactionCommon, Transaction,
};

use super::{
    execute::{TransactionExecutionOutput, TransactionExecutor},
    validate::ValidationError,
    BlockArgs,
};

type TxResponseFn = dyn Fn(&Transaction, &BlockArgs) -> VmExecutionResultAndLogs + Send + Sync;

pub struct MockTransactionExecutor {
    call_responses: Box<TxResponseFn>,
    tx_responses: Box<TxResponseFn>,
}

impl fmt::Debug for MockTransactionExecutor {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("MockTransactionExecutor")
            .finish_non_exhaustive()
    }
}

impl Default for MockTransactionExecutor {
    fn default() -> Self {
        Self {
            call_responses: Box::new(|tx, _| {
                panic!(
                    "Unexpected call with data {}",
                    hex::encode(tx.execute.calldata())
                );
            }),
            tx_responses: Box::new(|tx, _| {
                panic!("Unexpect transaction call: {tx:?}");
            }),
        }
    }
}

impl MockTransactionExecutor {
    #[cfg(test)]
    pub(crate) fn set_call_responses<F>(&mut self, responses: F)
    where
        F: Fn(&Transaction, &BlockArgs) -> ExecutionResult + 'static + Send + Sync,
    {
        self.call_responses = self.wrap_responses(responses);
    }

    #[cfg(test)]
    pub(crate) fn set_tx_responses<F>(&mut self, responses: F)
    where
        F: Fn(&Transaction, &BlockArgs) -> ExecutionResult + 'static + Send + Sync,
    {
        self.tx_responses = self.wrap_responses(responses);
    }

    #[cfg(test)]
    fn wrap_responses<F>(&mut self, responses: F) -> Box<TxResponseFn>
    where
        F: Fn(&Transaction, &BlockArgs) -> ExecutionResult + 'static + Send + Sync,
    {
        Box::new(
            move |tx: &Transaction, ba: &BlockArgs| -> VmExecutionResultAndLogs {
                VmExecutionResultAndLogs {
                    result: responses(tx, ba),
                    logs: Default::default(),
                    statistics: Default::default(),
                    refunds: Default::default(),
                }
            },
        )
    }

    #[cfg(test)]
    pub(crate) fn set_tx_responses_with_logs<F>(&mut self, responses: F)
    where
        F: Fn(&Transaction, &BlockArgs) -> VmExecutionResultAndLogs + 'static + Send + Sync,
    {
        self.tx_responses = Box::new(responses);
    }

    pub(crate) fn validate_tx(
        &self,
        tx: L2Tx,
        block_args: &BlockArgs,
    ) -> Result<(), ValidationError> {
        let result = (self.tx_responses)(&tx.into(), block_args);
        match result.result {
            ExecutionResult::Success { .. } => Ok(()),
            other => Err(ValidationError::Internal(anyhow::anyhow!(
                "transaction validation failed: {other:?}"
            ))),
        }
    }

    pub(crate) fn execute_tx(
        &self,
        tx: &Transaction,
        block_args: &BlockArgs,
    ) -> anyhow::Result<TransactionExecutionOutput> {
        let result = self.get_execution_result(tx, block_args);
        let output = TransactionExecutionOutput {
            vm: result,
            metrics: TransactionExecutionMetrics::default(),
            are_published_bytecodes_ok: true,
        };

        Ok(output)
    }

    fn get_execution_result(
        &self,
        tx: &Transaction,
        block_args: &BlockArgs,
    ) -> VmExecutionResultAndLogs {
        if let ExecuteTransactionCommon::L2(data) = &tx.common_data {
            if data.input.is_none() {
                return (self.call_responses)(tx, block_args);
            }
        }
        (self.tx_responses)(tx, block_args)
    }
}

impl From<MockTransactionExecutor> for TransactionExecutor {
    fn from(executor: MockTransactionExecutor) -> Self {
        Self::Mock(executor)
    }
}