Skip to content

Commit

Permalink
feat: add sendRawTransactionWithDetailedOutput API (#1806)
Browse files Browse the repository at this point in the history
Add a new `zks_sendRawTransactionWithDetailedOutput` API that instantly
returns storage logs and events (like if the transaction would have
already been applied) alongside the transaction hash. The API is
behaviourally analogous to `eth_sendRawTransaction` but with some extra
data returned from it.

This feature will allow consumer apps to apply "optimistic" events in
their applications instantly without having to wait for zkSync block
confirmation time.

It’s expected that optimistic logs of two uncommitted transaction that
modify the same state will not have causal relationships between each
other.

---------

Co-authored-by: Fedor Sakharov <[email protected]>
  • Loading branch information
ischasny and montekki authored Apr 30, 2024
1 parent e9d41a6 commit 6a30a31
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 27 deletions.
16 changes: 16 additions & 0 deletions core/lib/types/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,3 +736,19 @@ pub struct Proof {
pub address: Address,
pub storage_proof: Vec<StorageProof>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionDetailedResult {
pub transaction_hash: H256,
pub storage_logs: Vec<ApiStorageLog>,
pub events: Vec<Log>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiStorageLog {
pub address: Address,
pub key: U256,
pub written_value: U256,
}
25 changes: 23 additions & 2 deletions core/lib/types/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ use zksync_utils::{
};

use crate::{
api::Log,
ethabi,
l2_to_l1_log::L2ToL1Log,
tokens::{TokenInfo, TokenMetadata},
web3::types::Index,
zk_evm_types::{LogQuery, Timestamp},
Address, L1BatchNumber, CONTRACT_DEPLOYER_ADDRESS, H256, KNOWN_CODES_STORAGE_ADDRESS,
L1_MESSENGER_ADDRESS, U256,
Address, Bytes, L1BatchNumber, CONTRACT_DEPLOYER_ADDRESS, H256, KNOWN_CODES_STORAGE_ADDRESS,
L1_MESSENGER_ADDRESS, U256, U64,
};

#[cfg(test)]
Expand All @@ -41,6 +43,25 @@ impl VmEvent {
}
}

impl From<&VmEvent> for Log {
fn from(vm_event: &VmEvent) -> Self {
Log {
address: vm_event.address,
topics: vm_event.indexed_topics.clone(),
data: Bytes::from(vm_event.value.clone()),
block_hash: None,
block_number: None,
l1_batch_number: Some(U64::from(vm_event.location.0 .0)),
transaction_hash: None,
transaction_index: Some(Index::from(vm_event.location.1)),
log_index: None,
transaction_log_index: None,
log_type: None,
removed: Some(false),
}
}
}

pub static DEPLOY_EVENT_SIGNATURE: Lazy<H256> = Lazy::new(|| {
ethabi::long_signature(
"ContractDeployed",
Expand Down
13 changes: 12 additions & 1 deletion core/lib/types/src/storage/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use zksync_basic_types::AccountTreeId;
use zksync_utils::u256_to_h256;

use crate::{
api::ApiStorageLog,
zk_evm_types::{LogQuery, Timestamp},
StorageKey, StorageValue, U256,
};
Expand Down Expand Up @@ -80,7 +81,7 @@ impl StorageLog {
}
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum StorageLogQueryType {
Read,
InitialWrite,
Expand All @@ -93,3 +94,13 @@ pub struct StorageLogQuery {
pub log_query: LogQuery,
pub log_type: StorageLogQueryType,
}

impl From<&StorageLogQuery> for ApiStorageLog {
fn from(log_query: &StorageLogQuery) -> Self {
ApiStorageLog {
address: log_query.log_query.address,
key: log_query.log_query.key,
written_value: log_query.log_query.written_value,
}
}
}
10 changes: 8 additions & 2 deletions core/lib/web3_decl/src/namespaces/zks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc};
use zksync_types::{
api::{
BlockDetails, BridgeAddresses, L1BatchDetails, L2ToL1LogProof, Proof, ProtocolVersion,
TransactionDetails,
TransactionDetailedResult, TransactionDetails,
},
fee::Fee,
fee_model::FeeParams,
transaction_request::CallRequest,
Address, L1BatchNumber, L2BlockNumber, H256, U256, U64,
};

use crate::types::Token;
use crate::types::{Bytes, Token};

#[cfg_attr(
all(feature = "client", feature = "server"),
Expand Down Expand Up @@ -121,4 +121,10 @@ pub trait ZksNamespace {
keys: Vec<H256>,
l1_batch_number: L1BatchNumber,
) -> RpcResult<Option<Proof>>;

#[method(name = "sendRawTransactionWithDetailedOutput")]
async fn send_raw_transaction_with_detailed_output(
&self,
tx_bytes: Bytes,
) -> RpcResult<TransactionDetailedResult>;
}
43 changes: 33 additions & 10 deletions core/lib/zksync_core/src/api_server/execution_sandbox/testonly.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use super::{
BlockArgs,
};

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

pub(crate) struct MockTransactionExecutor {
call_responses: Box<TxResponseFn>,
Expand Down Expand Up @@ -47,19 +47,42 @@ impl MockTransactionExecutor {
where
F: Fn(&Transaction, &BlockArgs) -> ExecutionResult + 'static + Send + Sync,
{
self.call_responses = Box::new(responses);
self.call_responses = self.wrap_responses(responses);
}

pub 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);
}

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(),
}
},
)
}

pub 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 fn validate_tx(&self, tx: L2Tx, block_args: &BlockArgs) -> Result<(), ValidationError> {
let result = (self.tx_responses)(&tx.into(), block_args);
match result {
match result.result {
ExecutionResult::Success { .. } => Ok(()),
other => Err(ValidationError::Internal(anyhow::anyhow!(
"transaction validation failed: {other:?}"
Expand All @@ -74,19 +97,19 @@ impl MockTransactionExecutor {
) -> anyhow::Result<TransactionExecutionOutput> {
let result = self.get_execution_result(tx, block_args);
let output = TransactionExecutionOutput {
vm: VmExecutionResultAndLogs {
result,
logs: Default::default(),
statistics: Default::default(),
refunds: Default::default(),
},
vm: result,
metrics: TransactionExecutionMetrics::default(),
are_published_bytecodes_ok: true,
};

Ok(output)
}

fn get_execution_result(&self, tx: &Transaction, block_args: &BlockArgs) -> ExecutionResult {
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);
Expand Down
9 changes: 6 additions & 3 deletions core/lib/zksync_core/src/api_server/tx_sender/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,10 @@ impl TxSender {
}

#[tracing::instrument(level = "debug", skip_all, fields(tx.hash = ?tx.hash()))]
pub async fn submit_tx(&self, tx: L2Tx) -> Result<L2TxSubmissionResult, SubmitTxError> {
pub async fn submit_tx(
&self,
tx: L2Tx,
) -> Result<(L2TxSubmissionResult, VmExecutionResultAndLogs), SubmitTxError> {
let tx_hash = tx.hash();
let stage_latency = SANDBOX_METRICS.start_tx_submit_stage(tx_hash, SubmitTxStage::Validate);
let mut connection = self.acquire_replica_connection().await?;
Expand Down Expand Up @@ -407,11 +410,11 @@ impl TxSender {
L2TxSubmissionResult::Proxied => {
stage_latency.set_stage(SubmitTxStage::TxProxy);
stage_latency.observe();
Ok(submission_res_handle)
Ok((submission_res_handle, execution_output.vm))
}
_ => {
stage_latency.observe();
Ok(submission_res_handle)
Ok((submission_res_handle, execution_output.vm))
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/lib/zksync_core/src/api_server/tx_sender/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ async fn submitting_tx_requires_one_connection() {
let (tx_sender, _) = create_test_tx_sender(pool.clone(), l2_chain_id, tx_executor).await;

let submission_result = tx_sender.submit_tx(tx).await.unwrap();
assert_matches!(submission_result, L2TxSubmissionResult::Added);
assert_matches!(submission_result.0, L2TxSubmissionResult::Added);

let mut storage = pool.connection().await.unwrap();
storage
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use std::collections::HashMap;

use itertools::Itertools;
use zksync_types::{
api::{
BlockDetails, BridgeAddresses, L1BatchDetails, L2ToL1LogProof, Proof, ProtocolVersion,
TransactionDetails,
ApiStorageLog, BlockDetails, BridgeAddresses, L1BatchDetails, L2ToL1LogProof, Log, Proof,
ProtocolVersion, TransactionDetailedResult, TransactionDetails,
},
fee::Fee,
fee_model::FeeParams,
transaction_request::CallRequest,
Address, L1BatchNumber, L2BlockNumber, H256, U256, U64,
Address, Bytes, L1BatchNumber, L2BlockNumber, StorageLogQueryType, H256, U256, U64,
};
use zksync_web3_decl::{
jsonrpsee::core::{async_trait, RpcResult},
Expand Down Expand Up @@ -174,4 +175,35 @@ impl ZksNamespaceServer for ZksNamespace {
self.get_base_token_l1_address_impl()
.map_err(|err| self.current_method().map_err(err))
}

async fn send_raw_transaction_with_detailed_output(
&self,
tx_bytes: Bytes,
) -> RpcResult<TransactionDetailedResult> {
self.send_raw_transaction_with_detailed_output_impl(tx_bytes)
.await
.map(|result| TransactionDetailedResult {
transaction_hash: result.0,
storage_logs: result
.1
.logs
.storage_logs
.iter()
.filter(|x| x.log_type != StorageLogQueryType::Read)
.map(ApiStorageLog::from)
.collect_vec(),
events: result
.1
.logs
.events
.iter()
.map(|x| {
let mut l = Log::from(x);
l.transaction_hash = Some(result.0);
l
})
.collect_vec(),
})
.map_err(|err| self.current_method().map_err(err))
}
}
21 changes: 19 additions & 2 deletions core/lib/zksync_core/src/api_server/web3/namespaces/zks.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{collections::HashMap, convert::TryInto};

use anyhow::Context as _;
use multivm::interface::VmExecutionResultAndLogs;
use zksync_dal::{Connection, Core, CoreDal, DalError};
use zksync_mini_merkle_tree::MiniMerkleTree;
use zksync_system_constants::DEFAULT_L2_TX_GAS_PER_PUBDATA_BYTE;
Expand All @@ -17,7 +18,7 @@ use zksync_types::{
tokens::ETHEREUM_ADDRESS,
transaction_request::CallRequest,
utils::storage_key_for_standard_token_balance,
AccountTreeId, L1BatchNumber, L2BlockNumber, ProtocolVersionId, StorageKey, Transaction,
AccountTreeId, Bytes, L1BatchNumber, L2BlockNumber, ProtocolVersionId, StorageKey, Transaction,
L1_MESSENGER_ADDRESS, L2_BASE_TOKEN_ADDRESS, REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_BYTE, U256, U64,
};
use zksync_utils::{address_to_h256, h256_to_u256};
Expand All @@ -28,7 +29,7 @@ use zksync_web3_decl::{

use crate::api_server::{
tree::TreeApiError,
web3::{backend_jsonrpsee::MethodTracer, RpcState},
web3::{backend_jsonrpsee::MethodTracer, metrics::API_METRICS, RpcState},
};

#[derive(Debug)]
Expand Down Expand Up @@ -537,4 +538,20 @@ impl ZksNamespace {
.base_token_address
.ok_or(Web3Error::NotImplemented)
}

#[tracing::instrument(skip(self, tx_bytes))]
pub async fn send_raw_transaction_with_detailed_output_impl(
&self,
tx_bytes: Bytes,
) -> Result<(H256, VmExecutionResultAndLogs), Web3Error> {
let (mut tx, hash) = self.state.parse_transaction_bytes(&tx_bytes.0)?;
tx.set_input(tx_bytes.0, hash);

let submit_result = self.state.tx_sender.submit_tx(tx).await;
submit_result.map(|result| (hash, result.1)).map_err(|err| {
tracing::debug!("Send raw transaction error: {err}");
API_METRICS.submit_tx_error[&err.prom_error_code()].inc();
err.into()
})
}
}
Loading

0 comments on commit 6a30a31

Please sign in to comment.