use std::marker::PhantomData;
use borsh::BorshDeserialize;
use sov_modules_api::{BasicAddress, BlobReaderTrait, Context, DaSpec, DispatchCall};
use sov_rollup_interface::stf::{BatchReceipt, TransactionReceipt};
use sov_state::StateCheckpoint;
use tracing::{debug, error};
use crate::tx_verifier::{verify_txs_stateless, TransactionAndRawHash};
use crate::{Batch, Runtime, SequencerOutcome, SlashingReason, TxEffect};
type ApplyBatchResult<T, A> = Result<T, ApplyBatchError<A>>;
#[allow(type_alias_bounds)]
type ApplyBatch<Da: DaSpec> = ApplyBatchResult<
BatchReceipt<SequencerOutcome<<Da::BlobTransaction as BlobReaderTrait>::Address>, TxEffect>,
<Da::BlobTransaction as BlobReaderTrait>::Address,
>;
#[cfg(all(target_os = "zkvm", feature = "bench"))]
use sov_zk_cycle_macros::cycle_tracker;
pub struct AppTemplate<C: Context, Da: DaSpec, Vm, RT: Runtime<C, Da>> {
pub current_storage: C::Storage,
pub runtime: RT,
pub(crate) checkpoint: Option<StateCheckpoint<C::Storage>>,
phantom_vm: PhantomData<Vm>,
phantom_da: PhantomData<Da>,
}
pub(crate) enum ApplyBatchError<A: BasicAddress> {
Ignored([u8; 32]),
Slashed {
hash: [u8; 32],
reason: SlashingReason,
sequencer_da_address: A,
},
}
impl<A: BasicAddress> From<ApplyBatchError<A>> for BatchReceipt<SequencerOutcome<A>, TxEffect> {
fn from(value: ApplyBatchError<A>) -> Self {
match value {
ApplyBatchError::Ignored(hash) => BatchReceipt {
batch_hash: hash,
tx_receipts: Vec::new(),
inner: SequencerOutcome::Ignored,
},
ApplyBatchError::Slashed {
hash,
reason,
sequencer_da_address,
} => BatchReceipt {
batch_hash: hash,
tx_receipts: Vec::new(),
inner: SequencerOutcome::Slashed {
reason,
sequencer_da_address,
},
},
}
}
}
impl<C, Vm, Da, RT> AppTemplate<C, Da, Vm, RT>
where
C: Context,
Da: DaSpec,
RT: Runtime<C, Da>,
{
pub fn new(storage: C::Storage, runtime: RT) -> Self {
Self {
runtime,
current_storage: storage,
checkpoint: None,
phantom_vm: PhantomData,
phantom_da: PhantomData,
}
}
#[cfg_attr(all(target_os = "zkvm", feature = "bench"), cycle_tracker)]
pub(crate) fn apply_blob(&mut self, blob: &mut Da::BlobTransaction) -> ApplyBatch<Da> {
debug!(
"Applying batch from sequencer: 0x{}",
hex::encode(blob.sender())
);
let mut batch_workspace = self
.checkpoint
.take()
.expect("Working_set was initialized in begin_slot")
.to_revertable();
if let Err(e) = self.runtime.begin_blob_hook(blob, &mut batch_workspace) {
error!(
"Error: The batch was rejected by the 'begin_blob_hook' hook. Skipping batch without slashing the sequencer: {}",
e
);
self.checkpoint = Some(batch_workspace.revert());
return Err(ApplyBatchError::Ignored(blob.hash()));
}
batch_workspace = batch_workspace.checkpoint().to_revertable();
let _ = batch_workspace.take_events();
let (txs, messages) = match self.pre_process_batch(blob) {
Ok((txs, messages)) => (txs, messages),
Err(reason) => {
let mut batch_workspace = batch_workspace.revert().to_revertable();
let sequencer_da_address = blob.sender();
let sequencer_outcome = SequencerOutcome::Slashed {
reason,
sequencer_da_address: sequencer_da_address.clone(),
};
match self
.runtime
.end_blob_hook(sequencer_outcome, &mut batch_workspace)
{
Ok(()) => {
self.checkpoint = Some(batch_workspace.checkpoint());
}
Err(e) => {
error!("End blob hook failed: {}", e);
self.checkpoint = Some(batch_workspace.revert());
}
};
return Err(ApplyBatchError::Slashed {
hash: blob.hash(),
reason,
sequencer_da_address,
});
}
};
assert_eq!(
txs.len(),
messages.len(),
"Error in preprocessing batch, there should be same number of txs and messages"
);
let mut tx_receipts = Vec::with_capacity(txs.len());
for (TransactionAndRawHash { tx, raw_tx_hash }, msg) in
txs.into_iter().zip(messages.into_iter())
{
let sender_address = match self.runtime.pre_dispatch_tx_hook(&tx, &mut batch_workspace)
{
Ok(verified_tx) => verified_tx,
Err(e) => {
error!("Stateful verification error - the sequencer included an invalid transaction: {}", e);
let receipt = TransactionReceipt {
tx_hash: raw_tx_hash,
body_to_save: None,
events: batch_workspace.take_events(),
receipt: TxEffect::Reverted,
};
tx_receipts.push(receipt);
continue;
}
};
batch_workspace = batch_workspace.checkpoint().to_revertable();
let ctx = C::new(sender_address.clone());
let tx_result = self.runtime.dispatch_call(msg, &mut batch_workspace, &ctx);
let events = batch_workspace.take_events();
let tx_effect = match tx_result {
Ok(_) => TxEffect::Successful,
Err(e) => {
error!(
"Tx 0x{} was reverted error: {}",
hex::encode(raw_tx_hash),
e
);
batch_workspace = batch_workspace.revert().to_revertable();
TxEffect::Reverted
}
};
debug!("Tx {} effect: {:?}", hex::encode(raw_tx_hash), tx_effect);
let receipt = TransactionReceipt {
tx_hash: raw_tx_hash,
body_to_save: None,
events,
receipt: tx_effect,
};
tx_receipts.push(receipt);
batch_workspace = batch_workspace.checkpoint().to_revertable();
self.runtime
.post_dispatch_tx_hook(&tx, &mut batch_workspace)
.expect("Impossible happened: error in post_dispatch_tx_hook");
}
let sequencer_outcome = SequencerOutcome::Rewarded(0);
if let Err(e) = self
.runtime
.end_blob_hook(sequencer_outcome.clone(), &mut batch_workspace)
{
error!("Failed on `end_blob_hook`: {}", e);
};
self.checkpoint = Some(batch_workspace.checkpoint());
Ok(BatchReceipt {
batch_hash: blob.hash(),
tx_receipts,
inner: sequencer_outcome,
})
}
fn pre_process_batch(
&self,
blob_data: &mut impl BlobReaderTrait,
) -> Result<
(
Vec<TransactionAndRawHash<C>>,
Vec<<RT as DispatchCall>::Decodable>,
),
SlashingReason,
> {
let batch = self.deserialize_batch(blob_data)?;
debug!("Deserialized batch with {} txs", batch.txs.len());
let txs = self.verify_txs_stateless(batch)?;
let messages = self.decode_txs(&txs)?;
Ok((txs, messages))
}
fn deserialize_batch(
&self,
blob_data: &mut impl BlobReaderTrait,
) -> Result<Batch, SlashingReason> {
match Batch::try_from_slice(data_for_deserialization(blob_data)) {
Ok(batch) => Ok(batch),
Err(e) => {
assert_eq!(blob_data.verified_data().len(), blob_data.total_len(), "Batch deserialization failed and some data was not provided. The prover might be malicious");
error!(
"Unable to deserialize batch provided by the sequencer {}",
e
);
Err(SlashingReason::InvalidBatchEncoding)
}
}
}
fn verify_txs_stateless(
&self,
batch: Batch,
) -> Result<Vec<TransactionAndRawHash<C>>, SlashingReason> {
match verify_txs_stateless(batch.txs) {
Ok(txs) => Ok(txs),
Err(e) => {
error!("Stateless verification error - the sequencer included a transaction which was known to be invalid. {}\n", e);
Err(SlashingReason::StatelessVerificationFailed)
}
}
}
fn decode_txs(
&self,
txs: &[TransactionAndRawHash<C>],
) -> Result<Vec<<RT as DispatchCall>::Decodable>, SlashingReason> {
let mut decoded_messages = Vec::with_capacity(txs.len());
for TransactionAndRawHash { tx, raw_tx_hash } in txs {
match RT::decode_call(tx.runtime_msg()) {
Ok(msg) => decoded_messages.push(msg),
Err(e) => {
error!("Tx 0x{} decoding error: {}", hex::encode(raw_tx_hash), e);
return Err(SlashingReason::InvalidTransactionEncoding);
}
}
}
Ok(decoded_messages)
}
}
#[cfg(feature = "native")]
fn data_for_deserialization(blob: &mut impl BlobReaderTrait) -> &[u8] {
blob.full_data()
}
#[cfg(not(feature = "native"))]
fn data_for_deserialization(blob: &mut impl BlobReaderTrait) -> &[u8] {
blob.verified_data()
}