1use crate::{TempoBlockExecutionCtx, evm::TempoEvm};
2use alloy_consensus::{Transaction, transaction::TxHashRef};
3use alloy_evm::{
4 Database, Evm, RecoveredTx,
5 block::{
6 BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockValidationError,
7 ExecutableTx, OnStateHook, TxResult,
8 },
9 eth::{
10 EthBlockExecutor, EthTxResult,
11 receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx},
12 },
13};
14use alloy_primitives::{Address, B256, U256};
15use alloy_rlp::Decodable;
16use commonware_codec::DecodeExt;
17use commonware_cryptography::{
18 Verifier,
19 ed25519::{PublicKey, Signature},
20};
21use reth_evm::block::StateDB;
22use reth_revm::{
23 Inspector,
24 context::result::ResultAndState,
25 context_interface::JournalTr,
26 state::{Account, Bytecode, EvmState},
27};
28use std::collections::{HashMap, HashSet};
29use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardforks};
30use tempo_contracts::precompiles::VALIDATOR_CONFIG_V2_ADDRESS;
31use tempo_primitives::{
32 SubBlock, SubBlockMetadata, TempoReceipt, TempoTxEnvelope, TempoTxType,
33 subblock::PartialValidatorKey,
34};
35use tempo_revm::{TempoHaltReason, evm::TempoContext};
36use tracing::trace;
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
39pub(crate) enum BlockSection {
40 StartOfBlock,
42 NonShared,
46 SubBlock { proposer: PartialValidatorKey },
48 GasIncentive,
50 System { seen_subblocks_signatures: bool },
52}
53
54#[derive(Debug, Clone, Copy, Default)]
56#[non_exhaustive]
57pub struct TempoReceiptBuilder;
58
59impl ReceiptBuilder for TempoReceiptBuilder {
60 type Transaction = TempoTxEnvelope;
61 type Receipt = TempoReceipt;
62
63 fn build_receipt<E: Evm>(&self, ctx: ReceiptBuilderCtx<'_, TempoTxType, E>) -> Self::Receipt {
64 let ReceiptBuilderCtx {
65 tx_type,
66 result,
67 cumulative_gas_used,
68 ..
69 } = ctx;
70 TempoReceipt {
71 tx_type,
72 success: result.is_success(),
75 cumulative_gas_used,
76 logs: result.into_logs(),
77 }
78 }
79}
80
81#[derive(Debug)]
85pub(crate) struct TempoTxResult<H> {
86 inner: EthTxResult<H, TempoTxType>,
88 next_section: BlockSection,
90 is_payment: bool,
92 tx: Option<TempoTxEnvelope>,
97}
98
99impl<H> TxResult for TempoTxResult<H> {
100 type HaltReason = H;
101
102 fn result(&self) -> &ResultAndState<Self::HaltReason> {
103 self.inner.result()
104 }
105}
106
107pub(crate) struct TempoBlockExecutor<'a, DB: Database, I> {
113 pub(crate) inner:
114 EthBlockExecutor<'a, TempoEvm<DB, I>, &'a TempoChainSpec, TempoReceiptBuilder>,
115
116 section: BlockSection,
117 seen_subblocks: Vec<(PartialValidatorKey, Vec<TempoTxEnvelope>)>,
118 validator_set: Option<Vec<B256>>,
119 shared_gas_limit: u64,
120 subblock_fee_recipients: HashMap<PartialValidatorKey, Address>,
121
122 non_shared_gas_left: u64,
123 non_payment_gas_left: u64,
124 incentive_gas_used: u64,
125}
126
127impl<'a, DB, I> TempoBlockExecutor<'a, DB, I>
128where
129 DB: StateDB,
130 I: Inspector<TempoContext<DB>>,
131{
132 pub(crate) fn new(
133 evm: TempoEvm<DB, I>,
134 ctx: TempoBlockExecutionCtx<'a>,
135 chain_spec: &'a TempoChainSpec,
136 ) -> Self {
137 Self {
138 incentive_gas_used: 0,
139 validator_set: ctx.validator_set,
140 non_payment_gas_left: ctx.general_gas_limit,
141 non_shared_gas_left: evm.block().gas_limit - ctx.shared_gas_limit,
142 shared_gas_limit: ctx.shared_gas_limit,
143 inner: EthBlockExecutor::new(
144 evm,
145 ctx.inner,
146 chain_spec,
147 TempoReceiptBuilder::default(),
148 ),
149 section: BlockSection::StartOfBlock,
150 seen_subblocks: Vec::new(),
151 subblock_fee_recipients: ctx.subblock_fee_recipients,
152 }
153 }
154
155 pub(crate) fn validate_system_tx(
157 &self,
158 tx: &TempoTxEnvelope,
159 ) -> Result<BlockSection, BlockValidationError> {
160 let block = self.evm().block();
161 let block_number = block.number.to_be_bytes_vec();
162 let to = tx.to().unwrap_or_default();
163
164 let mut seen_subblocks_signatures = match self.section {
166 BlockSection::System {
167 seen_subblocks_signatures,
168 } => seen_subblocks_signatures,
169 _ => false,
170 };
171
172 if to.is_zero() {
173 if seen_subblocks_signatures {
174 return Err(BlockValidationError::msg(
175 "duplicate subblocks metadata system transaction",
176 ));
177 }
178
179 if tx.input().len() < U256::BYTES
180 || tx.input()[tx.input().len() - U256::BYTES..] != block_number
181 {
182 return Err(BlockValidationError::msg(
183 "invalid subblocks metadata system transaction",
184 ));
185 }
186
187 let mut buf = &tx.input()[..tx.input().len() - U256::BYTES];
188 let Ok(metadata) = Vec::<SubBlockMetadata>::decode(&mut buf) else {
189 return Err(BlockValidationError::msg(
190 "invalid subblocks metadata system transaction",
191 ));
192 };
193
194 if !buf.is_empty() {
195 return Err(BlockValidationError::msg(
196 "invalid subblocks metadata system transaction",
197 ));
198 }
199
200 self.validate_shared_gas(&metadata)?;
201
202 seen_subblocks_signatures = true;
203 } else {
204 return Err(BlockValidationError::msg("invalid system transaction"));
205 }
206
207 Ok(BlockSection::System {
208 seen_subblocks_signatures,
209 })
210 }
211
212 pub(crate) fn validate_shared_gas(
213 &self,
214 metadata: &[SubBlockMetadata],
215 ) -> Result<(), BlockValidationError> {
216 let Some(validator_set) = &self.validator_set else {
218 return Ok(());
219 };
220 let gas_per_subblock = self
221 .shared_gas_limit
222 .checked_div(validator_set.len() as u64)
223 .expect("validator set must not be empty");
224
225 let mut incentive_gas = 0;
226 let mut seen = HashSet::new();
227 let mut next_non_empty = 0;
228 for metadata in metadata {
229 if !validator_set.contains(&metadata.validator) {
230 return Err(BlockValidationError::msg("invalid subblock validator"));
231 }
232
233 if !seen.insert(metadata.validator) {
234 return Err(BlockValidationError::msg(
235 "only one subblock per validator is allowed",
236 ));
237 }
238
239 let transactions = if let Some((validator, txs)) =
240 self.seen_subblocks.get(next_non_empty)
241 && validator.matches(metadata.validator)
242 {
243 next_non_empty += 1;
244 txs.clone()
245 } else {
246 Vec::new()
247 };
248
249 let reserved_gas = transactions.iter().map(|tx| tx.gas_limit()).sum::<u64>();
250
251 let signature_hash = SubBlock {
252 version: metadata.version,
253 fee_recipient: metadata.fee_recipient,
254 parent_hash: self.inner.ctx.parent_hash,
255 transactions: transactions.clone(),
256 }
257 .signature_hash();
258
259 let Ok(validator) = PublicKey::decode(&mut metadata.validator.as_ref()) else {
260 return Err(BlockValidationError::msg("invalid subblock validator"));
261 };
262
263 let Ok(signature) = Signature::decode(&mut metadata.signature.as_ref()) else {
264 return Err(BlockValidationError::msg(
265 "invalid subblock signature encoding",
266 ));
267 };
268
269 if !validator.verify(&[], signature_hash.as_slice(), &signature) {
271 return Err(BlockValidationError::msg("invalid subblock signature"));
272 }
273
274 if reserved_gas > gas_per_subblock {
275 return Err(BlockValidationError::msg(
276 "subblock gas used exceeds gas per subblock",
277 ));
278 }
279
280 incentive_gas += gas_per_subblock - reserved_gas;
281 }
282
283 if next_non_empty != self.seen_subblocks.len() {
284 return Err(BlockValidationError::msg(
285 "failed to map all non-empty subblocks to metadata",
286 ));
287 }
288
289 if incentive_gas < self.incentive_gas_used {
290 return Err(BlockValidationError::msg("incentive gas limit exceeded"));
291 }
292
293 Ok(())
294 }
295
296 pub(crate) fn validate_tx(
297 &self,
298 tx: &TempoTxEnvelope,
299 gas_used: u64,
300 ) -> Result<BlockSection, BlockValidationError> {
301 if tx.is_system_tx() {
303 self.validate_system_tx(tx)
304 } else if let Some(tx_proposer) = tx.subblock_proposer() {
305 match self.section {
306 BlockSection::GasIncentive | BlockSection::System { .. } => {
307 Err(BlockValidationError::msg("subblock section already passed"))
308 }
309 BlockSection::StartOfBlock | BlockSection::NonShared => {
310 Ok(BlockSection::SubBlock {
311 proposer: tx_proposer,
312 })
313 }
314 BlockSection::SubBlock { proposer } => {
315 if proposer == tx_proposer
316 || !self.seen_subblocks.iter().any(|(p, _)| *p == tx_proposer)
317 {
318 Ok(BlockSection::SubBlock {
319 proposer: tx_proposer,
320 })
321 } else {
322 Err(BlockValidationError::msg(
323 "proposer's subblock already processed",
324 ))
325 }
326 }
327 }
328 } else {
329 match self.section {
330 BlockSection::StartOfBlock | BlockSection::NonShared => {
331 if gas_used > self.non_shared_gas_left
332 || (!tx.is_payment_v1() && gas_used > self.non_payment_gas_left)
333 {
334 Ok(BlockSection::GasIncentive)
338 } else {
339 Ok(BlockSection::NonShared)
340 }
341 }
342 BlockSection::SubBlock { .. } => {
343 Ok(BlockSection::GasIncentive)
346 }
347 BlockSection::GasIncentive => Ok(BlockSection::GasIncentive),
348 BlockSection::System { .. } => {
349 trace!(target: "tempo::block", tx_hash = ?*tx.tx_hash(), "Rejecting: regular transaction after system transaction");
350 Err(BlockValidationError::msg(
351 "regular transaction can't follow system transaction",
352 ))
353 }
354 }
355 }
356 }
357}
358
359impl<'a, DB, I> BlockExecutor for TempoBlockExecutor<'a, DB, I>
360where
361 DB: StateDB,
362 I: Inspector<TempoContext<DB>>,
363{
364 type Transaction = TempoTxEnvelope;
365 type Receipt = TempoReceipt;
366 type Evm = TempoEvm<DB, I>;
367 type Result = TempoTxResult<TempoHaltReason>;
368
369 fn apply_pre_execution_changes(&mut self) -> Result<(), alloy_evm::block::BlockExecutionError> {
370 self.inner.apply_pre_execution_changes()?;
371
372 let timestamp = self.evm().block().timestamp.to::<u64>();
374 if self.inner.spec.is_t2_active_at_timestamp(timestamp) {
375 let db = self.evm_mut().ctx_mut().journaled_state.db_mut();
376 let mut info = db
377 .basic(VALIDATOR_CONFIG_V2_ADDRESS)
378 .map_err(BlockExecutionError::other)?
379 .unwrap_or_default();
380 if info.is_empty_code_hash() {
381 let code = Bytecode::new_legacy([0xef].into());
382 info.code_hash = code.hash_slow();
383 info.code = Some(code);
384 let mut account: Account = info.into();
385 account.mark_touch();
386 db.commit(EvmState::from_iter([(
387 VALIDATOR_CONFIG_V2_ADDRESS,
388 account,
389 )]));
390 }
391 }
392
393 Ok(())
394 }
395
396 fn receipts(&self) -> &[Self::Receipt] {
397 self.inner.receipts()
398 }
399
400 fn execute_transaction_without_commit(
401 &mut self,
402 tx: impl ExecutableTx<Self>,
403 ) -> Result<Self::Result, BlockExecutionError> {
404 let (tx_env, recovered) = tx.into_parts();
405
406 let beneficiary = self.evm_mut().ctx_mut().block.beneficiary;
407 if let Some(validator) = recovered.tx().subblock_proposer() {
409 let fee_recipient = *self
410 .subblock_fee_recipients
411 .get(&validator)
412 .ok_or(BlockExecutionError::msg("invalid subblock transaction"))?;
413
414 self.evm_mut().ctx_mut().block.beneficiary = fee_recipient;
415 }
416 let result = self
417 .inner
418 .execute_transaction_without_commit((tx_env, &recovered));
419
420 self.evm_mut().ctx_mut().block.beneficiary = beneficiary;
421
422 let inner = result?;
423
424 let next_section = self.validate_tx(recovered.tx(), inner.result.result.gas_used())?;
425 Ok(TempoTxResult {
426 inner,
427 next_section,
428 is_payment: recovered.tx().is_payment_v1(),
429 tx: matches!(next_section, BlockSection::SubBlock { .. })
430 .then(|| recovered.tx().clone()),
431 })
432 }
433
434 fn commit_transaction(&mut self, output: Self::Result) -> Result<u64, BlockExecutionError> {
435 let TempoTxResult {
436 inner,
437 next_section,
438 is_payment,
439 tx,
440 } = output;
441
442 let gas_used = self.inner.commit_transaction(inner)?;
443
444 let logs = self.inner.evm.take_revert_logs();
448 if !logs.is_empty() {
449 self.inner
450 .receipts
451 .last_mut()
452 .expect("receipt was just pushed")
453 .logs
454 .extend(logs);
455 }
456
457 self.section = next_section;
458
459 match self.section {
460 BlockSection::StartOfBlock => {
461 }
463 BlockSection::NonShared => {
464 self.non_shared_gas_left -= gas_used;
465 if !is_payment {
466 self.non_payment_gas_left -= gas_used;
467 }
468 }
469 BlockSection::SubBlock { proposer } => {
470 let last_subblock = if let Some(last) = self
471 .seen_subblocks
472 .last_mut()
473 .filter(|(p, _)| *p == proposer)
474 {
475 last
476 } else {
477 self.seen_subblocks.push((proposer, Vec::new()));
478 self.seen_subblocks.last_mut().unwrap()
479 };
480
481 last_subblock.1.push(tx.ok_or_else(|| {
482 BlockExecutionError::msg("missing tx for subblock transaction")
483 })?);
484 }
485 BlockSection::GasIncentive => {
486 self.incentive_gas_used += gas_used;
487 }
488 BlockSection::System { .. } => {
489 }
491 }
492
493 Ok(gas_used)
494 }
495
496 fn finish(
497 self,
498 ) -> Result<(Self::Evm, BlockExecutionResult<Self::Receipt>), BlockExecutionError> {
499 self.inner.finish()
500 }
501
502 fn set_state_hook(&mut self, hook: Option<Box<dyn OnStateHook>>) {
503 self.inner.set_state_hook(hook)
504 }
505
506 fn evm_mut(&mut self) -> &mut Self::Evm {
507 self.inner.evm_mut()
508 }
509
510 fn evm(&self) -> &Self::Evm {
511 self.inner.evm()
512 }
513}
514
515#[cfg(test)]
517impl<'a, DB, I> TempoBlockExecutor<'a, DB, I>
518where
519 DB: Database,
520 I: Inspector<TempoContext<DB>>,
521{
522 pub(crate) fn set_section_for_test(&mut self, section: BlockSection) {
524 self.section = section;
525 }
526
527 pub(crate) fn add_seen_subblock_for_test(
529 &mut self,
530 proposer: PartialValidatorKey,
531 txs: Vec<TempoTxEnvelope>,
532 ) {
533 self.seen_subblocks.push((proposer, txs));
534 }
535
536 pub(crate) fn set_incentive_gas_used_for_test(&mut self, gas: u64) {
538 self.incentive_gas_used = gas;
539 }
540
541 pub(crate) fn section(&self) -> BlockSection {
543 self.section
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550 use crate::test_utils::{TestExecutorBuilder, test_chainspec, test_evm};
551 use alloy_consensus::{Signed, TxLegacy};
552 use alloy_evm::{block::BlockExecutor, eth::receipt_builder::ReceiptBuilder};
553 use alloy_primitives::{Bytes, Log, Signature, TxKind, bytes::BytesMut};
554 use alloy_rlp::Encodable;
555 use commonware_cryptography::{Signer, ed25519::PrivateKey};
556 use reth_chainspec::EthChainSpec;
557 use reth_revm::State;
558 use revm::{
559 context::result::{ExecutionResult, ResultGas},
560 database::EmptyDB,
561 };
562 use tempo_primitives::{
563 SubBlockMetadata, TempoSignature, TempoTransaction, TempoTxType,
564 subblock::{SubBlockVersion, TEMPO_SUBBLOCK_NONCE_KEY_PREFIX},
565 transaction::{Call, envelope::TEMPO_SYSTEM_TX_SIGNATURE},
566 };
567 use tempo_revm::TempoHaltReason;
568
569 fn create_legacy_tx() -> TempoTxEnvelope {
570 let tx = TxLegacy {
571 chain_id: Some(1),
572 nonce: 0,
573 gas_price: 1,
574 gas_limit: 21000,
575 to: TxKind::Call(Address::ZERO),
576 value: U256::ZERO,
577 input: Bytes::new(),
578 };
579 TempoTxEnvelope::Legacy(Signed::new_unhashed(tx, Signature::test_signature()))
580 }
581
582 #[test]
583 fn test_build_receipt() {
584 let builder = TempoReceiptBuilder;
585 let tx = create_legacy_tx();
586 let evm = test_evm(EmptyDB::default());
587
588 let logs = vec![Log::new_unchecked(
589 Address::ZERO,
590 vec![B256::ZERO],
591 Bytes::new(),
592 )];
593 let result: ExecutionResult<TempoHaltReason> = ExecutionResult::Success {
594 reason: revm::context::result::SuccessReason::Return,
595 gas: ResultGas::default().with_limit(21000).with_spent(21000),
596 logs,
597 output: revm::context::result::Output::Call(Bytes::new()),
598 };
599
600 let cumulative_gas_used = 21000;
601
602 let receipt = builder.build_receipt(ReceiptBuilderCtx {
603 tx_type: tx.tx_type(),
604 evm: &evm,
605 result,
606 state: &Default::default(),
607 cumulative_gas_used,
608 });
609
610 assert_eq!(receipt.tx_type, TempoTxType::Legacy);
611 assert!(receipt.success);
612 assert_eq!(receipt.cumulative_gas_used, 21000);
613 assert_eq!(receipt.logs.len(), 1);
614 assert_eq!(receipt.logs[0].address, Address::ZERO);
615 }
616
617 #[test]
618 fn test_validate_system_tx() {
619 let chainspec = test_chainspec();
620 let mut db = State::builder().with_bundle_update().build();
621 let executor = TestExecutorBuilder::default().build(&mut db, &chainspec);
622
623 let signer = PrivateKey::from_seed(0);
624 let metadata = vec![create_valid_subblock_metadata(B256::ZERO, &signer)];
625 let input = create_system_tx_input(metadata, 1);
626 let system_tx = create_system_tx(chainspec.chain().id(), input);
627
628 let result = executor.validate_system_tx(&system_tx);
629 assert!(
630 result.is_ok(),
631 "validate_system_tx failed: {:?}",
632 result.err()
633 );
634 assert_eq!(
635 result.unwrap(),
636 BlockSection::System {
637 seen_subblocks_signatures: true
638 }
639 );
640 }
641
642 fn create_system_tx_input(metadata: Vec<SubBlockMetadata>, block_number: u64) -> Bytes {
643 let mut input = BytesMut::new();
644 metadata.encode(&mut input);
645 input.extend_from_slice(&U256::from(block_number).to_be_bytes::<32>());
646 input.freeze().into()
647 }
648
649 fn create_system_tx(chain_id: u64, input: Bytes) -> TempoTxEnvelope {
650 TempoTxEnvelope::Legacy(Signed::new_unhashed(
651 TxLegacy {
652 chain_id: Some(chain_id),
653 nonce: 0,
654 gas_price: 0,
655 gas_limit: 0,
656 to: TxKind::Call(Address::ZERO),
657 value: U256::ZERO,
658 input,
659 },
660 TEMPO_SYSTEM_TX_SIGNATURE,
661 ))
662 }
663
664 fn create_valid_subblock_metadata(parent_hash: B256, signer: &PrivateKey) -> SubBlockMetadata {
665 let validator_key = B256::from_slice(&signer.public_key());
666 let subblock = tempo_primitives::SubBlock {
667 version: SubBlockVersion::V1,
668 parent_hash,
669 fee_recipient: Address::ZERO,
670 transactions: vec![],
671 };
672 let signature_hash = subblock.signature_hash();
673 let signature = signer.sign(&[], signature_hash.as_slice());
674
675 SubBlockMetadata {
676 version: SubBlockVersion::V1,
677 validator: validator_key,
678 fee_recipient: Address::ZERO,
679 signature: Bytes::copy_from_slice(signature.as_ref()),
680 }
681 }
682
683 #[test]
684 fn test_validate_system_tx_duplicate_subblocks_system_tx() {
685 let chainspec = test_chainspec();
686 let mut db = State::builder().with_bundle_update().build();
687 let executor = TestExecutorBuilder::default()
688 .with_section(BlockSection::System {
689 seen_subblocks_signatures: true,
690 })
691 .build(&mut db, &chainspec);
692
693 let signer = PrivateKey::from_seed(0);
694 let metadata = vec![create_valid_subblock_metadata(B256::ZERO, &signer)];
695 let input = create_system_tx_input(metadata, 1);
696 let system_tx = create_system_tx(chainspec.chain().id(), input);
697
698 let result = executor.validate_system_tx(&system_tx);
699 assert!(result.is_err());
700 assert_eq!(
701 result.unwrap_err().to_string(),
702 "duplicate subblocks metadata system transaction"
703 );
704 }
705
706 #[test]
707 fn test_validate_system_tx_invalid_sublocks_metadata() {
708 let chainspec = test_chainspec();
709 let mut db = State::builder().with_bundle_update().build();
710 let executor = TestExecutorBuilder::default().build(&mut db, &chainspec);
711
712 let mut input = BytesMut::new();
713 input.extend_from_slice(&[0xff, 0xff, 0xff]); input.extend_from_slice(&U256::from(1u64).to_be_bytes::<32>());
715 let system_tx = create_system_tx(chainspec.chain().id(), input.freeze().into());
716
717 let result = executor.validate_system_tx(&system_tx);
718 assert!(result.is_err());
719 assert_eq!(
720 result.unwrap_err().to_string(),
721 "invalid subblocks metadata system transaction"
722 );
723 }
724
725 #[test]
726 fn test_validate_system_tx_invalid_system_tx() {
727 let chainspec = test_chainspec();
728 let mut db = State::builder().with_bundle_update().build();
729 let executor = TestExecutorBuilder::default().build(&mut db, &chainspec);
730
731 let system_tx = TempoTxEnvelope::Legacy(Signed::new_unhashed(
733 TxLegacy {
734 chain_id: Some(chainspec.chain().id()),
735 nonce: 0,
736 gas_price: 0,
737 gas_limit: 0,
738 to: TxKind::Call(Address::repeat_byte(0x01)), value: U256::ZERO,
740 input: Bytes::new(),
741 },
742 TEMPO_SYSTEM_TX_SIGNATURE,
743 ));
744
745 let result = executor.validate_system_tx(&system_tx);
746 assert!(result.is_err());
747 assert_eq!(
748 result.unwrap_err().to_string(),
749 "invalid system transaction"
750 );
751 }
752
753 #[test]
754 fn test_validate_shared_gas() {
755 let chainspec = test_chainspec();
756 let mut db = State::builder().with_bundle_update().build();
757 let signer = PrivateKey::from_seed(0);
758 let validator_key = B256::from_slice(&signer.public_key());
759 let executor = TestExecutorBuilder::default()
760 .with_validator_set(vec![validator_key])
761 .build(&mut db, &chainspec);
762
763 let metadata = vec![create_valid_subblock_metadata(B256::ZERO, &signer)];
764 let result = executor.validate_shared_gas(&metadata);
765 assert!(result.is_ok());
766 }
767
768 #[test]
769 fn test_validate_shared_gas_set_does_not_contain_validator() {
770 let chainspec = test_chainspec();
771 let mut db = State::builder().with_bundle_update().build();
772 let signer = PrivateKey::from_seed(0);
773 let different_validator = B256::repeat_byte(0x42); let executor = TestExecutorBuilder::default()
775 .with_validator_set(vec![different_validator])
776 .build(&mut db, &chainspec);
777
778 let metadata = vec![create_valid_subblock_metadata(B256::ZERO, &signer)];
779 let result = executor.validate_shared_gas(&metadata);
780 assert!(result.is_err());
781 assert_eq!(
782 result.unwrap_err().to_string(),
783 "invalid subblock validator"
784 );
785 }
786
787 #[test]
788 fn test_validate_shared_gas_more_than_one_subblock_per_validator() {
789 let chainspec = test_chainspec();
790 let mut db = State::builder().with_bundle_update().build();
791 let signer = PrivateKey::from_seed(0);
792 let validator_key = B256::from_slice(&signer.public_key());
793 let executor = TestExecutorBuilder::default()
794 .with_validator_set(vec![validator_key])
795 .build(&mut db, &chainspec);
796
797 let m = create_valid_subblock_metadata(B256::ZERO, &signer);
799 let metadata = vec![m.clone(), m];
800
801 let result = executor.validate_shared_gas(&metadata);
802 assert!(result.is_err());
803 assert_eq!(
804 result.unwrap_err().to_string(),
805 "only one subblock per validator is allowed"
806 );
807 }
808
809 #[test]
810 fn test_validate_shared_gas_invalid_signature_encoding() {
811 let chainspec = test_chainspec();
812 let mut db = State::builder().with_bundle_update().build();
813 let signer = PrivateKey::from_seed(0);
814 let validator_key = B256::from_slice(&signer.public_key());
815 let executor = TestExecutorBuilder::default()
816 .with_validator_set(vec![validator_key])
817 .build(&mut db, &chainspec);
818
819 let metadata = vec![SubBlockMetadata {
821 version: SubBlockVersion::V1,
822 validator: validator_key,
823 fee_recipient: Address::ZERO,
824 signature: Bytes::from_static(&[0x01, 0x02, 0x03]),
825 }];
826
827 let result = executor.validate_shared_gas(&metadata);
828 assert!(result.is_err());
829 assert_eq!(
830 result.unwrap_err().to_string(),
831 "invalid subblock signature encoding"
832 );
833 }
834
835 #[test]
836 fn test_validate_shared_gas_invalid_signature() {
837 let chainspec = test_chainspec();
838 let mut db = State::builder().with_bundle_update().build();
839 let signer = PrivateKey::from_seed(0);
840 let validator_key = B256::from_slice(&signer.public_key());
841 let executor = TestExecutorBuilder::default()
842 .with_validator_set(vec![validator_key])
843 .build(&mut db, &chainspec);
844
845 let wrong_signer = PrivateKey::from_seed(1);
847 let subblock = tempo_primitives::SubBlock {
848 version: SubBlockVersion::V1,
849 parent_hash: B256::ZERO,
850 fee_recipient: Address::ZERO,
851 transactions: vec![],
852 };
853 let signature_hash = subblock.signature_hash();
854 let wrong_signature = wrong_signer.sign(&[], signature_hash.as_slice());
855
856 let metadata = vec![SubBlockMetadata {
857 version: SubBlockVersion::V1,
858 validator: validator_key, fee_recipient: Address::ZERO,
860 signature: Bytes::copy_from_slice(wrong_signature.as_ref()), }];
862
863 let result = executor.validate_shared_gas(&metadata);
864 assert!(result.is_err());
865 assert_eq!(
866 result.unwrap_err().to_string(),
867 "invalid subblock signature"
868 );
869 }
870
871 #[test]
872 fn test_validate_shared_gas_gas_used_exceeds_gas_per_subblock() {
873 let chainspec = test_chainspec();
874 let mut db = State::builder().with_bundle_update().build();
875 let signer = PrivateKey::from_seed(0);
876 let validator_key = B256::from_slice(&signer.public_key());
877 let tx = create_legacy_tx();
878 let proposer = PartialValidatorKey::from_slice(&validator_key[..15]);
879
880 let subblock = tempo_primitives::SubBlock {
882 version: SubBlockVersion::V1,
883 parent_hash: B256::ZERO,
884 fee_recipient: Address::ZERO,
885 transactions: vec![tx.clone()],
886 };
887
888 let executor = TestExecutorBuilder::default()
889 .with_validator_set(vec![validator_key])
890 .with_shared_gas_limit(100) .with_seen_subblock(proposer, vec![tx])
892 .build(&mut db, &chainspec);
893 let signature_hash = subblock.signature_hash();
894 let signature = signer.sign(&[], signature_hash.as_slice());
895
896 let metadata = vec![SubBlockMetadata {
897 version: SubBlockVersion::V1,
898 validator: validator_key,
899 fee_recipient: Address::ZERO,
900 signature: Bytes::copy_from_slice(signature.as_ref()),
901 }];
902
903 let result = executor.validate_shared_gas(&metadata);
904 assert!(result.is_err());
905 assert_eq!(
906 result.unwrap_err().to_string(),
907 "subblock gas used exceeds gas per subblock"
908 );
909 }
910
911 #[test]
912 fn test_validate_shared_gas_unexpected_subblock_len() {
913 let chainspec = test_chainspec();
914 let mut db = State::builder().with_bundle_update().build();
915 let signer = PrivateKey::from_seed(0);
916 let validator_key = B256::from_slice(&signer.public_key());
917
918 let different_key = B256::repeat_byte(0x99);
920 let different_proposer = PartialValidatorKey::from_slice(&different_key[..15]);
921
922 let executor = TestExecutorBuilder::default()
923 .with_validator_set(vec![validator_key])
924 .with_seen_subblock(different_proposer, vec![])
925 .build(&mut db, &chainspec);
926
927 let metadata = vec![create_valid_subblock_metadata(B256::ZERO, &signer)];
929
930 let result = executor.validate_shared_gas(&metadata);
931 assert!(result.is_err());
932 assert_eq!(
933 result.unwrap_err().to_string(),
934 "failed to map all non-empty subblocks to metadata"
935 );
936 }
937
938 #[test]
939 fn test_validate_shared_gas_limit_exceeded() {
940 let chainspec = test_chainspec();
941 let mut db = State::builder().with_bundle_update().build();
942 let signer = PrivateKey::from_seed(0);
943 let validator_key = B256::from_slice(&signer.public_key());
944
945 let executor = TestExecutorBuilder::default()
947 .with_validator_set(vec![validator_key])
948 .with_incentive_gas_used(100_000_000)
949 .build(&mut db, &chainspec);
950
951 let metadata = vec![create_valid_subblock_metadata(B256::ZERO, &signer)];
952
953 let result = executor.validate_shared_gas(&metadata);
954 assert!(result.is_err());
955 assert_eq!(
956 result.unwrap_err().to_string(),
957 "incentive gas limit exceeded"
958 );
959 }
960
961 #[test]
962 fn test_validate_tx() {
963 let chainspec = test_chainspec();
964 let mut db = State::builder().with_bundle_update().build();
965 let executor = TestExecutorBuilder::default().build(&mut db, &chainspec);
966
967 let tx = create_legacy_tx();
969 let result = executor.validate_tx(&tx, 21000);
970 assert!(result.is_ok());
971 assert_eq!(result.unwrap(), BlockSection::NonShared);
972 }
973
974 fn create_subblock_tx(proposer: &PartialValidatorKey) -> TempoTxEnvelope {
975 let mut nonce_bytes = [0u8; 32];
976 nonce_bytes[0] = TEMPO_SUBBLOCK_NONCE_KEY_PREFIX;
977 nonce_bytes[1..16].copy_from_slice(proposer.as_slice());
978
979 let tx = TempoTransaction {
980 chain_id: 1,
981 calls: vec![Call {
982 to: Address::ZERO.into(),
983 input: Default::default(),
984 value: Default::default(),
985 }],
986 gas_limit: 21000,
987 nonce_key: U256::from_be_bytes(nonce_bytes),
988 max_fee_per_gas: 1,
989 max_priority_fee_per_gas: 1,
990 ..Default::default()
991 };
992
993 let signature = TempoSignature::from(Signature::test_signature());
994 TempoTxEnvelope::AA(tx.into_signed(signature))
995 }
996
997 #[test]
998 fn test_validate_tx_subblock_section_already_passed() {
999 let chainspec = test_chainspec();
1000 let mut db = State::builder().with_bundle_update().build();
1001 let signer = PrivateKey::from_seed(0);
1002 let validator_key = B256::from_slice(&signer.public_key());
1003 let proposer = PartialValidatorKey::from_slice(&validator_key[..15]);
1004
1005 let executor = TestExecutorBuilder::default()
1007 .with_section(BlockSection::GasIncentive)
1008 .build(&mut db, &chainspec);
1009
1010 let subblock_tx = create_subblock_tx(&proposer);
1011 let result = executor.validate_tx(&subblock_tx, 21000);
1012 assert!(result.is_err());
1013 assert_eq!(
1014 result.unwrap_err().to_string(),
1015 "subblock section already passed"
1016 );
1017
1018 let mut db2 = State::builder().with_bundle_update().build();
1020 let executor2 = TestExecutorBuilder::default()
1021 .with_section(BlockSection::System {
1022 seen_subblocks_signatures: false,
1023 })
1024 .build(&mut db2, &chainspec);
1025
1026 let result = executor2.validate_tx(&subblock_tx, 21000);
1027 assert!(result.is_err());
1028 assert_eq!(
1029 result.unwrap_err().to_string(),
1030 "subblock section already passed"
1031 );
1032 }
1033
1034 #[test]
1035 fn test_validate_tx_proposer_subblock_already_processed() {
1036 let chainspec = test_chainspec();
1037 let mut db = State::builder().with_bundle_update().build();
1038 let signer1 = PrivateKey::from_seed(0);
1039 let validator_key1 = B256::from_slice(&signer1.public_key());
1040 let proposer1 = PartialValidatorKey::from_slice(&validator_key1[..15]);
1041
1042 let signer2 = PrivateKey::from_seed(1);
1043 let validator_key2 = B256::from_slice(&signer2.public_key());
1044 let proposer2 = PartialValidatorKey::from_slice(&validator_key2[..15]);
1045
1046 let executor = TestExecutorBuilder::default()
1048 .with_section(BlockSection::SubBlock {
1049 proposer: proposer2,
1050 })
1051 .with_seen_subblock(proposer1, vec![])
1052 .build(&mut db, &chainspec);
1053
1054 let subblock_tx = create_subblock_tx(&proposer1);
1056 let result = executor.validate_tx(&subblock_tx, 21000);
1057 assert!(result.is_err());
1058 assert_eq!(
1059 result.unwrap_err().to_string(),
1060 "proposer's subblock already processed"
1061 );
1062 }
1063
1064 #[test]
1065 fn test_validate_tx_regular_tx_follow_system_tx() {
1066 let chainspec = test_chainspec();
1067 let mut db = State::builder().with_bundle_update().build();
1068
1069 let executor = TestExecutorBuilder::default()
1071 .with_section(BlockSection::System {
1072 seen_subblocks_signatures: false,
1073 })
1074 .build(&mut db, &chainspec);
1075
1076 let tx = create_legacy_tx();
1078 let result = executor.validate_tx(&tx, 21000);
1079 assert!(result.is_err());
1080 assert_eq!(
1081 result.unwrap_err().to_string(),
1082 "regular transaction can't follow system transaction"
1083 );
1084 }
1085
1086 #[test]
1087 fn test_commit_transaction() {
1088 let chainspec = test_chainspec();
1089 let mut db = State::builder().with_bundle_update().build();
1090 let mut executor = TestExecutorBuilder::default()
1091 .with_general_gas_limit(30_000_000)
1092 .with_parent_beacon_block_root(B256::ZERO)
1093 .build(&mut db, &chainspec);
1094
1095 executor.apply_pre_execution_changes().unwrap();
1097
1098 let tx = create_legacy_tx();
1099 let output = TempoTxResult {
1100 inner: EthTxResult {
1101 result: ResultAndState {
1102 result: revm::context::result::ExecutionResult::Success {
1103 reason: revm::context::result::SuccessReason::Return,
1104 gas: ResultGas::default().with_limit(21000).with_spent(21000),
1105 logs: vec![],
1106 output: revm::context::result::Output::Call(Bytes::new()),
1107 },
1108 state: Default::default(),
1109 },
1110 blob_gas_used: 0,
1111 tx_type: tx.tx_type(),
1112 },
1113 next_section: BlockSection::NonShared,
1114 is_payment: false,
1115 tx: None,
1116 };
1117
1118 let gas_used = executor.commit_transaction(output).unwrap();
1119
1120 assert_eq!(gas_used, 21000);
1121 assert_eq!(executor.section(), BlockSection::NonShared);
1122 }
1123
1124 #[test]
1125 fn test_finish() {
1126 let chainspec = test_chainspec();
1127 let mut db = State::builder().with_bundle_update().build();
1128 let executor = TestExecutorBuilder::default().build(&mut db, &chainspec);
1129
1130 let result = executor.finish();
1131 assert!(result.is_ok());
1132 }
1133
1134 #[test]
1135 fn test_apply_pre_execution_deploys_validator_v2_code() {
1136 use std::sync::Arc;
1137 use tempo_chainspec::spec::DEV;
1138
1139 let chainspec = Arc::new(TempoChainSpec::from_genesis(DEV.genesis().clone()));
1141 let mut db = State::builder().with_bundle_update().build();
1142 let mut executor = TestExecutorBuilder::default()
1143 .with_parent_beacon_block_root(B256::ZERO)
1144 .build(&mut db, &chainspec);
1145
1146 executor.apply_pre_execution_changes().unwrap();
1147
1148 let acc = db.load_cache_account(VALIDATOR_CONFIG_V2_ADDRESS).unwrap();
1149 let info = acc.account_info().unwrap();
1150 assert!(!info.is_empty_code_hash());
1151 }
1152}