1#[allow(clippy::arithmetic_side_effects)]
2#[allow(clippy::cast_possible_truncation)]
3#[allow(non_snake_case)]
4#[cfg(test)]
5mod tests {
6 #[cfg(not(feature = "wasm-executor"))]
7 use fuel_core_executor::{
8 executor::{
9 TimeoutOnlyTxWaiter,
10 TransparentPreconfirmationSender,
11 WaitNewTransactionsResult,
12 },
13 ports::{
14 NewTxWaiterPort,
15 PreconfirmationSenderPort,
16 },
17 };
18
19 #[cfg(not(feature = "wasm-executor"))]
20 use fuel_core_types::services::preconfirmation::{
21 Preconfirmation,
22 PreconfirmationStatus,
23 };
24
25 use crate as fuel_core;
26 use fuel_core::database::Database;
27 use fuel_core_executor::{
28 executor::OnceTransactionsSource,
29 ports::{
30 MaybeCheckedTransaction,
31 RelayerPort,
32 },
33 refs::ContractRef,
34 };
35 use fuel_core_storage::{
36 Result as StorageResult,
37 StorageAsMut,
38 StorageAsRef,
39 tables::{
40 Coins,
41 ConsensusParametersVersions,
42 ContractsRawCode,
43 Messages,
44 },
45 transactional::{
46 AtomicView,
47 WriteTransaction,
48 },
49 };
50 use fuel_core_types::{
51 blockchain::{
52 block::{
53 Block,
54 PartialFuelBlock,
55 },
56 header::{
57 ApplicationHeader,
58 ConsensusHeader,
59 PartialBlockHeader,
60 },
61 primitives::DaBlockHeight,
62 },
63 entities::{
64 coins::coin::CompressedCoin,
65 relayer::message::{
66 Message,
67 MessageV1,
68 },
69 },
70 fuel_asm::{
71 GTFArgs,
72 RegId,
73 op,
74 },
75 fuel_crypto::SecretKey,
76 fuel_merkle::common::empty_sum_sha256,
77 fuel_tx::{
78 Bytes32,
79 ConsensusParameters,
80 Create,
81 DependentCost,
82 FeeParameters,
83 Finalizable,
84 GasCostsValues,
85 Output,
86 Receipt,
87 Script,
88 Transaction,
89 TransactionBuilder,
90 TransactionFee,
91 TxParameters,
92 TxPointer,
93 UniqueIdentifier,
94 UtxoId,
95 ValidityError,
96 consensus_parameters::gas::GasCostsValuesV1,
97 field::{
98 Expiration,
99 InputContract,
100 Inputs,
101 MintAmount,
102 MintAssetId,
103 MintGasPrice,
104 OutputContract,
105 Outputs,
106 Policies,
107 TxPointer as TxPointerTraitTrait,
108 },
109 input::{
110 Input,
111 coin::{
112 CoinPredicate,
113 CoinSigned,
114 },
115 contract,
116 },
117 policies::PolicyType,
118 },
119 fuel_types::{
120 Address,
121 AssetId,
122 BlockHeight,
123 ChainId,
124 ContractId,
125 Word,
126 canonical::Serialize,
127 },
128 fuel_vm::{
129 Call,
130 CallFrame,
131 Contract,
132 checked_transaction::{
133 CheckError,
134 EstimatePredicates,
135 IntoChecked,
136 },
137 interpreter::{
138 ExecutableTransaction,
139 MemoryInstance,
140 },
141 predicate::EmptyStorage,
142 script_with_data_offset,
143 util::test_helpers::TestBuilder as TxBuilder,
144 },
145 services::{
146 block_producer::Components,
147 executor::{
148 Error as ExecutorError,
149 Event as ExecutorEvent,
150 ExecutionResult,
151 TransactionExecutionResult,
152 TransactionValidityError,
153 },
154 relayer::Event,
155 },
156 tai64::Tai64,
157 test_helpers::create_contract,
158 };
159 use fuel_core_upgradable_executor::executor::Executor;
160 use itertools::Itertools;
161 use rand::{
162 Rng,
163 SeedableRng,
164 prelude::StdRng,
165 };
166 use sha2::{
167 Digest,
168 Sha256,
169 };
170
171 #[derive(Clone, Debug, Default)]
172 struct Config {
173 pub consensus_parameters: ConsensusParameters,
177 pub forbid_fake_coins_default: bool,
179 }
180
181 #[derive(Clone, Debug)]
182 struct DisabledRelayer;
183
184 impl RelayerPort for DisabledRelayer {
185 fn enabled(&self) -> bool {
186 false
187 }
188
189 fn get_events(&self, _: &DaBlockHeight) -> anyhow::Result<Vec<Event>> {
190 unimplemented!()
191 }
192 }
193
194 impl AtomicView for DisabledRelayer {
195 type LatestView = Self;
196
197 fn latest_view(&self) -> StorageResult<Self::LatestView> {
198 Ok(self.clone())
199 }
200 }
201
202 fn add_consensus_parameters(
203 mut database: Database,
204 consensus_parameters: &ConsensusParameters,
205 ) -> Database {
206 let mut tx = database.write_transaction();
208 tx.storage_as_mut::<ConsensusParametersVersions>()
209 .insert(&0, consensus_parameters)
210 .unwrap();
211 tx.commit().unwrap();
212 database
213 }
214
215 fn create_executor(
216 database: Database,
217 config: Config,
218 ) -> Executor<Database, DisabledRelayer> {
219 let executor_config = fuel_core_upgradable_executor::config::Config {
220 forbid_fake_coins_default: config.forbid_fake_coins_default,
221 allow_syscall: true,
222 native_executor_version: None,
223 allow_historical_execution: true,
224 };
225
226 let database = add_consensus_parameters(database, &config.consensus_parameters);
227
228 Executor::new(database, DisabledRelayer, executor_config)
229 }
230
231 pub(crate) fn setup_executable_script() -> (Create, Script) {
232 let mut rng = StdRng::seed_from_u64(2322);
233 let asset_id: AssetId = rng.r#gen();
234 let owner: Address = rng.r#gen();
235 let input_amount = 1000;
236 let variable_transfer_amount = 100;
237 let coin_output_amount = 150;
238
239 let (create, contract_id) = create_contract(
240 vec![
241 op::addi(0x10, RegId::FP, CallFrame::a_offset().try_into().unwrap()),
243 op::lw(0x10, 0x10, 0),
244 op::addi(0x11, RegId::FP, CallFrame::b_offset().try_into().unwrap()),
246 op::lw(0x11, 0x11, 0),
247 op::addi(0x12, 0x11, 32),
249 op::addi(0x13, RegId::ZERO, 0),
251 op::tro(0x12, 0x13, 0x10, 0x11),
252 op::ret(RegId::ONE),
253 ]
254 .into_iter()
255 .collect::<Vec<u8>>()
256 .as_slice(),
257 &mut rng,
258 );
259 let (script, data_offset) = script_with_data_offset!(
260 data_offset,
261 vec![
262 op::movi(0x10, data_offset + 64),
264 op::movi(0x11, data_offset),
266 op::movi(0x12, variable_transfer_amount),
268 op::call(0x10, 0x12, 0x11, RegId::CGAS),
270 op::ret(RegId::ONE),
271 ],
272 TxParameters::DEFAULT.tx_offset()
273 );
274
275 let script_data: Vec<u8> = [
276 asset_id.as_ref(),
277 owner.as_ref(),
278 Call::new(
279 contract_id,
280 variable_transfer_amount as Word,
281 data_offset as Word,
282 )
283 .to_bytes()
284 .as_ref(),
285 ]
286 .into_iter()
287 .flatten()
288 .copied()
289 .collect();
290
291 let script = TxBuilder::new(2322)
292 .script_gas_limit(TxParameters::DEFAULT.max_gas_per_tx() >> 1)
293 .start_script(script, script_data)
294 .contract_input(contract_id)
295 .coin_input(asset_id, input_amount)
296 .variable_output(Default::default())
297 .coin_output(asset_id, coin_output_amount)
298 .change_output(asset_id)
299 .contract_output(&contract_id)
300 .build()
301 .transaction()
302 .clone();
303
304 (create, script)
305 }
306
307 pub(crate) fn test_block(
308 block_height: BlockHeight,
309 da_block_height: DaBlockHeight,
310 num_txs: usize,
311 ) -> Block {
312 let transactions = (1..num_txs + 1).map(script_tx_for_amount).collect_vec();
313
314 let mut block = Block::default();
315 block.header_mut().set_block_height(block_height);
316 block.header_mut().set_da_height(da_block_height);
317 *block.transactions_mut() = transactions;
318 block
319 }
320
321 fn script_tx_for_amount(amount: usize) -> Transaction {
322 let asset = AssetId::BASE;
323 TxBuilder::new(2322u64)
324 .script_gas_limit(10)
325 .coin_input(asset, (amount as Word) * 100)
326 .coin_output(asset, (amount as Word) * 50)
327 .change_output(asset)
328 .build()
329 .transaction()
330 .to_owned()
331 .into()
332 }
333
334 #[test]
336 fn executor_validates_correctly_produced_block() {
337 let mut producer = create_executor(Default::default(), Default::default());
338 let verifier = create_executor(Default::default(), Default::default());
339 let block = test_block(1u32.into(), 0u64.into(), 10);
340
341 let ExecutionResult {
342 block,
343 skipped_transactions,
344 ..
345 } = producer.produce_and_commit(block.into()).unwrap();
346
347 let validation_result = verifier.validate(&block);
348 assert!(validation_result.is_ok());
349 assert!(skipped_transactions.is_empty());
350 }
351
352 #[test]
354 fn executor_commits_transactions_to_block() {
355 let mut producer = create_executor(Default::default(), Default::default());
356 let block = test_block(1u32.into(), 0u64.into(), 10);
357 let start_block = block.clone();
358
359 let ExecutionResult {
360 block,
361 skipped_transactions,
362 ..
363 } = producer.produce_and_commit(block.into()).unwrap();
364
365 assert!(skipped_transactions.is_empty());
366 assert_ne!(
367 start_block.header().transactions_root(),
368 block.header().transactions_root()
369 );
370 assert_eq!(block.transactions().len(), 11);
371 assert!(block.transactions()[10].as_mint().is_some());
372 if let Some(mint) = block.transactions()[10].as_mint() {
373 assert_eq!(
374 mint.tx_pointer(),
375 &TxPointer::new(*block.header().height(), 10)
376 );
377 assert_eq!(mint.mint_asset_id(), &AssetId::BASE);
378 assert_eq!(mint.mint_amount(), &0);
379 assert_eq!(mint.input_contract().contract_id, ContractId::zeroed());
380 assert_eq!(mint.input_contract().balance_root, Bytes32::zeroed());
381 assert_eq!(mint.input_contract().state_root, Bytes32::zeroed());
382 assert_eq!(mint.input_contract().utxo_id, UtxoId::default());
383 assert_eq!(mint.input_contract().tx_pointer, TxPointer::default());
384 assert_eq!(mint.output_contract().balance_root, Bytes32::zeroed());
385 assert_eq!(mint.output_contract().state_root, Bytes32::zeroed());
386 assert_eq!(mint.output_contract().input_index, 0);
387 } else {
388 panic!("Invalid outputs of coinbase");
389 }
390 }
391
392 mod coinbase {
393 use crate::graphql_api::ports::DatabaseContracts;
394
395 use super::*;
396 use fuel_core_storage::{
397 iter::IterDirection,
398 transactional::{
399 AtomicView,
400 Modifiable,
401 },
402 };
403 use fuel_core_types::services::graphql_api::ContractBalance;
404
405 #[test]
406 fn executor_commits_transactions_with_non_zero_coinbase_generation() {
407 let price = 1;
418 let amount = 10000;
419 let limit = 0;
420 let gas_price_factor = 1;
421 let script = TxBuilder::new(1u64)
422 .script_gas_limit(limit)
423 .max_fee_limit(amount)
424 .coin_input(AssetId::BASE, amount)
425 .change_output(AssetId::BASE)
426 .build()
427 .transaction()
428 .clone();
429
430 let recipient = Contract::EMPTY_CONTRACT_ID;
431
432 let fee_params =
433 FeeParameters::default().with_gas_price_factor(gas_price_factor);
434 let mut consensus_parameters = ConsensusParameters::default();
435 consensus_parameters.set_fee_params(fee_params);
436 let config = Config {
437 consensus_parameters: consensus_parameters.clone(),
438 ..Default::default()
439 };
440
441 let database = &mut Database::default();
442 database
443 .storage::<ContractsRawCode>()
444 .insert(&recipient, &[])
445 .expect("Should insert coinbase contract");
446
447 let mut producer = create_executor(database.clone(), config);
448
449 let expected_fee_amount_1 = TransactionFee::checked_from_tx(
450 consensus_parameters.gas_costs(),
451 consensus_parameters.fee_params(),
452 &script,
453 price,
454 )
455 .unwrap()
456 .max_fee();
457 let invalid_duplicate_tx = script.clone().into();
458
459 let mut header = PartialBlockHeader::default();
460 header.consensus.height = 1.into();
461
462 let (
463 ExecutionResult {
464 block,
465 skipped_transactions,
466 ..
467 },
468 changes,
469 ) = producer
470 .produce_without_commit_with_source_direct_resolve(Components {
471 header_to_produce: header,
472 transactions_source: OnceTransactionsSource::new(vec![
473 script.into(),
474 invalid_duplicate_tx,
475 ]),
476 gas_price: price,
477 coinbase_recipient: recipient,
478 })
479 .unwrap()
480 .into();
481 producer
482 .storage_view_provider
483 .commit_changes(changes)
484 .unwrap();
485
486 assert_eq!(skipped_transactions.len(), 1);
487 assert_eq!(block.transactions().len(), 2);
488 assert!(expected_fee_amount_1 > 0);
489 let first_mint;
490
491 let mut h = Sha256::new();
492 h.update(consensus_parameters.base_asset_id().as_ref());
493 h.update([0u8]);
494 let input_balances_hash = Bytes32::new(h.finalize().into());
495
496 let mut h = Sha256::new();
497 h.update(consensus_parameters.base_asset_id().as_ref());
498 h.update([1u8]);
499 h.update(expected_fee_amount_1.to_be_bytes());
500 let output_balances_hash = Bytes32::new(h.finalize().into());
501
502 let empty_hash = Bytes32::zeroed();
503
504 if let Some(mint) = block.transactions()[1].as_mint() {
505 assert_eq!(
506 mint.tx_pointer(),
507 &TxPointer::new(*block.header().height(), 1)
508 );
509 assert_eq!(mint.mint_asset_id(), &AssetId::BASE);
510 assert_eq!(mint.mint_amount(), &expected_fee_amount_1);
511 assert_eq!(mint.input_contract().contract_id, recipient);
512 assert_eq!(mint.input_contract().balance_root, input_balances_hash);
513 assert_eq!(mint.input_contract().state_root, empty_hash);
514 assert_eq!(mint.input_contract().utxo_id, UtxoId::default());
515 assert_eq!(mint.input_contract().tx_pointer, TxPointer::default());
516 assert_eq!(mint.output_contract().balance_root, output_balances_hash);
517 assert_eq!(mint.output_contract().state_root, empty_hash);
518 assert_eq!(mint.output_contract().input_index, 0);
519 first_mint = mint.clone();
520 } else {
521 panic!("Invalid coinbase transaction");
522 }
523
524 let ContractBalance {
525 asset_id, amount, ..
526 } = producer
527 .storage_view_provider
528 .latest_view()
529 .unwrap()
530 .contract_balances(recipient, None, IterDirection::Forward)
531 .next()
532 .unwrap()
533 .unwrap();
534 assert_eq!(asset_id, AssetId::zeroed());
535 assert_eq!(amount, expected_fee_amount_1);
536
537 let script = TxBuilder::new(2u64)
538 .script_gas_limit(limit)
539 .max_fee_limit(amount)
540 .coin_input(AssetId::BASE, amount)
541 .change_output(AssetId::BASE)
542 .build()
543 .transaction()
544 .clone();
545
546 let expected_fee_amount_2 = TransactionFee::checked_from_tx(
547 consensus_parameters.gas_costs(),
548 consensus_parameters.fee_params(),
549 &script,
550 price,
551 )
552 .unwrap()
553 .max_fee();
554
555 let mut header = PartialBlockHeader::default();
556 header.consensus.height = 2.into();
557
558 let (
559 ExecutionResult {
560 block,
561 skipped_transactions,
562 ..
563 },
564 changes,
565 ) = producer
566 .produce_without_commit_with_source_direct_resolve(Components {
567 header_to_produce: header,
568 transactions_source: OnceTransactionsSource::new(vec![script.into()]),
569 gas_price: price,
570 coinbase_recipient: recipient,
571 })
572 .unwrap()
573 .into();
574 producer
575 .storage_view_provider
576 .commit_changes(changes)
577 .unwrap();
578
579 assert_eq!(skipped_transactions.len(), 0);
580 assert_eq!(block.transactions().len(), 2);
581
582 if let Some(second_mint) = block.transactions()[1].as_mint() {
583 assert_eq!(second_mint.tx_pointer(), &TxPointer::new(2.into(), 1));
584 assert_eq!(second_mint.mint_asset_id(), &AssetId::BASE);
585 assert_eq!(second_mint.mint_amount(), &expected_fee_amount_2);
586 assert_eq!(second_mint.input_contract().contract_id, recipient);
587 assert_eq!(
588 second_mint.input_contract().utxo_id,
589 UtxoId::new(first_mint.id(&consensus_parameters.chain_id()), 0)
590 );
591 assert_eq!(
592 second_mint.input_contract().tx_pointer,
593 TxPointer::new(1.into(), 1)
594 );
595 assert_eq!(second_mint.output_contract().input_index, 0);
596 } else {
597 panic!("Invalid coinbase transaction");
598 }
599 let ContractBalance {
600 asset_id, amount, ..
601 } = producer
602 .storage_view_provider
603 .latest_view()
604 .unwrap()
605 .contract_balances(recipient, None, IterDirection::Forward)
606 .next()
607 .unwrap()
608 .unwrap();
609
610 assert_eq!(asset_id, AssetId::zeroed());
611 assert_eq!(amount, expected_fee_amount_1 + expected_fee_amount_2);
612 }
613
614 #[test]
615 fn skip_coinbase_during_dry_run() {
616 let price = 1;
617 let limit = 0;
618 let gas_price_factor = 1;
619 let script = TxBuilder::new(2322u64)
620 .script_gas_limit(limit)
621 .gas_price(price)
623 .coin_input(AssetId::BASE, 10000)
624 .change_output(AssetId::BASE)
625 .build()
626 .transaction()
627 .clone();
628
629 let fee_params =
630 FeeParameters::default().with_gas_price_factor(gas_price_factor);
631 let mut consensus_parameters = ConsensusParameters::default();
632 consensus_parameters.set_fee_params(fee_params);
633 let config = Config {
634 consensus_parameters,
635 ..Default::default()
636 };
637 let recipient = [1u8; 32].into();
638
639 let producer = create_executor(Default::default(), config);
640
641 let result = producer
642 .dry_run_without_commit_with_source(Components {
643 header_to_produce: Default::default(),
644 transactions_source: OnceTransactionsSource::new(vec![script.into()]),
645 coinbase_recipient: recipient,
646 gas_price: 0,
647 })
648 .unwrap();
649 let ExecutionResult { block, .. } = result.into_result();
650
651 assert_eq!(block.transactions().len(), 1);
652 }
653
654 #[test]
655 fn executor_commits_transactions_with_non_zero_coinbase_validation() {
656 let price = 1;
657 let amount = 10000;
658 let limit = 0;
659 let gas_price_factor = 1;
660 let script = TxBuilder::new(2322u64)
661 .script_gas_limit(limit)
662 .max_fee_limit(amount)
663 .coin_input(AssetId::BASE, 10000)
664 .change_output(AssetId::BASE)
665 .build()
666 .transaction()
667 .clone();
668 let recipient = Contract::EMPTY_CONTRACT_ID;
669
670 let fee_params =
671 FeeParameters::default().with_gas_price_factor(gas_price_factor);
672 let mut consensus_parameters = ConsensusParameters::default();
673 consensus_parameters.set_fee_params(fee_params);
674 let config = Config {
675 consensus_parameters,
676 ..Default::default()
677 };
678 let database = &mut Database::default();
679
680 database
681 .storage::<ContractsRawCode>()
682 .insert(&recipient, &[])
683 .expect("Should insert coinbase contract");
684
685 let producer = create_executor(database.clone(), config.clone());
686
687 let ExecutionResult {
688 block,
689 skipped_transactions,
690 ..
691 } = producer
692 .produce_without_commit_with_source_direct_resolve(Components {
693 header_to_produce: PartialBlockHeader::default(),
694 transactions_source: OnceTransactionsSource::new(vec![script.into()]),
695 gas_price: price,
696 coinbase_recipient: recipient,
697 })
698 .unwrap()
699 .into_result();
700 assert!(skipped_transactions.is_empty());
701 let produced_txs = block.transactions().to_vec();
702
703 let mut validator = create_executor(
704 Default::default(),
705 config,
707 );
708 let _ = validator.validate_and_commit(&block).unwrap();
709 assert_eq!(block.transactions(), produced_txs);
710 let ContractBalance {
711 asset_id, amount, ..
712 } = validator
713 .storage_view_provider
714 .latest_view()
715 .unwrap()
716 .contract_balances(recipient, None, IterDirection::Forward)
717 .next()
718 .unwrap()
719 .unwrap();
720 assert_eq!(asset_id, AssetId::zeroed());
721 assert_ne!(amount, 0);
722 }
723
724 #[test]
725 fn execute_cb_command() {
726 fn compare_coinbase_addresses(
727 config_coinbase: ContractId,
728 expected_in_tx_coinbase: ContractId,
729 ) -> bool {
730 let script = TxBuilder::new(2322u64)
731 .script_gas_limit(100000)
732 .gas_price(0)
734 .start_script(vec![
735 op::movi(0x11, Address::LEN.try_into().unwrap()),
737 op::aloc(0x11),
739 op::move_(0x10, RegId::HP),
742 op::cb(0x10),
744 op::gtf_args(0x12, 0x00, GTFArgs::ScriptData),
747 op::meq(0x13, 0x10, 0x12, 0x11),
752 op::ret(0x13),
754 ], expected_in_tx_coinbase.to_vec() )
755 .coin_input(AssetId::BASE, 1000)
756 .variable_output(Default::default())
757 .coin_output(AssetId::BASE, 1000)
758 .change_output(AssetId::BASE)
759 .build()
760 .transaction()
761 .clone();
762
763 let mut producer =
764 create_executor(Default::default(), Default::default());
765
766 let mut block = Block::default();
767 *block.transactions_mut() = vec![script.clone().into()];
768
769 let (ExecutionResult { tx_status, .. }, changes) = producer
770 .produce_without_commit_with_coinbase(
771 block.into(),
772 config_coinbase,
773 0,
774 )
775 .expect("Should execute the block")
776 .into();
777 producer
778 .storage_view_provider
779 .commit_changes(changes)
780 .unwrap();
781 let receipts = tx_status[0].result.receipts();
782
783 if let Some(Receipt::Return { val, .. }) = receipts.first() {
784 *val == 1
785 } else {
786 panic!("Execution of the `CB` script failed failed")
787 }
788 }
789
790 assert!(compare_coinbase_addresses(
791 ContractId::from([1u8; 32]),
792 ContractId::from([1u8; 32]),
793 ));
794 assert!(!compare_coinbase_addresses(
795 ContractId::from([9u8; 32]),
796 ContractId::from([1u8; 32]),
797 ));
798 assert!(!compare_coinbase_addresses(
799 ContractId::from([1u8; 32]),
800 ContractId::from([9u8; 32]),
801 ));
802 assert!(compare_coinbase_addresses(
803 ContractId::from([9u8; 32]),
804 ContractId::from([9u8; 32]),
805 ));
806 }
807
808 #[test]
809 fn invalidate_unexpected_index() {
810 let mint = Transaction::mint(
811 TxPointer::new(Default::default(), 1),
812 Default::default(),
813 Default::default(),
814 Default::default(),
815 Default::default(),
816 Default::default(),
817 );
818
819 let mut block = Block::default();
820 *block.transactions_mut() = vec![mint.into()];
821 block.header_mut().recalculate_metadata();
822
823 let mut validator = create_executor(
824 Default::default(),
825 Config {
826 forbid_fake_coins_default: false,
827 ..Default::default()
828 },
829 );
830 let validation_err = validator
831 .validate_and_commit(&block)
832 .expect_err("Expected error because coinbase if invalid");
833 assert!(matches!(
834 validation_err,
835 ExecutorError::MintHasUnexpectedIndex
836 ));
837 }
838
839 #[test]
840 fn invalidate_is_not_last() {
841 let mint = Transaction::mint(
842 TxPointer::new(Default::default(), 0),
843 Default::default(),
844 Default::default(),
845 Default::default(),
846 Default::default(),
847 Default::default(),
848 );
849 let tx = Transaction::default_test_tx();
850
851 let mut block = Block::default();
852 *block.transactions_mut() = vec![mint.clone().into(), tx, mint.into()];
853 block.header_mut().recalculate_metadata();
854
855 let mut validator = create_executor(Default::default(), Default::default());
856 let validation_err = validator
857 .validate_and_commit(&block)
858 .expect_err("Expected error because coinbase if invalid");
859 assert!(matches!(
860 validation_err,
861 ExecutorError::MintIsNotLastTransaction
862 ));
863 }
864
865 #[test]
866 fn invalidate_block_missed_coinbase() {
867 let block = Block::default();
868
869 let mut validator = create_executor(Default::default(), Default::default());
870 let validation_err = validator
871 .validate_and_commit(&block)
872 .expect_err("Expected error because coinbase is missing");
873 assert!(matches!(validation_err, ExecutorError::MintMissing));
874 }
875
876 #[test]
877 fn invalidate_block_height() {
878 let mint = Transaction::mint(
879 TxPointer::new(1.into(), Default::default()),
880 Default::default(),
881 Default::default(),
882 Default::default(),
883 Default::default(),
884 Default::default(),
885 );
886
887 let mut block = Block::default();
888 *block.transactions_mut() = vec![mint.into()];
889 block.header_mut().recalculate_metadata();
890
891 let mut validator = create_executor(Default::default(), Default::default());
892 let validation_err = validator
893 .validate_and_commit(&block)
894 .expect_err("Expected error because coinbase if invalid");
895
896 assert!(matches!(
897 validation_err,
898 ExecutorError::InvalidTransaction(CheckError::Validity(
899 ValidityError::TransactionMintIncorrectBlockHeight
900 ))
901 ));
902 }
903
904 #[test]
905 fn invalidate_invalid_base_asset() {
906 let mint = Transaction::mint(
907 TxPointer::new(Default::default(), Default::default()),
908 Default::default(),
909 Default::default(),
910 Default::default(),
911 Default::default(),
912 Default::default(),
913 );
914
915 let mut block = Block::default();
916 *block.transactions_mut() = vec![mint.into()];
917 block.header_mut().recalculate_metadata();
918
919 let mut consensus_parameters = ConsensusParameters::default();
920 consensus_parameters.set_base_asset_id([1u8; 32].into());
921
922 let config = Config {
923 consensus_parameters,
924 ..Default::default()
925 };
926 let mut validator = create_executor(Default::default(), config);
927 let validation_err = validator
928 .validate_and_commit(&block)
929 .expect_err("Expected error because coinbase if invalid");
930
931 assert!(matches!(
932 validation_err,
933 ExecutorError::InvalidTransaction(CheckError::Validity(
934 ValidityError::TransactionMintNonBaseAsset
935 ))
936 ));
937 }
938
939 #[test]
940 fn invalidate_mismatch_amount() {
941 let mint = Transaction::mint(
942 TxPointer::new(Default::default(), Default::default()),
943 Default::default(),
944 Default::default(),
945 123,
946 Default::default(),
947 Default::default(),
948 );
949
950 let mut block = Block::default();
951 *block.transactions_mut() = vec![mint.into()];
952 block.header_mut().recalculate_metadata();
953
954 let mut validator = create_executor(Default::default(), Default::default());
955 let validation_err = validator
956 .validate_and_commit(&block)
957 .expect_err("Expected error because coinbase if invalid");
958 assert!(matches!(
959 validation_err,
960 ExecutorError::CoinbaseAmountMismatch
961 ));
962 }
963 }
964
965 #[test]
966 fn executor_invalidates_expired_tx() {
967 let producer = create_executor(Default::default(), Default::default());
968 let validator = create_executor(Default::default(), Default::default());
969
970 let mut block = test_block(2u32.into(), 0u64.into(), 0);
972
973 let amount = 1;
974 let asset = AssetId::BASE;
975 let mut tx = TxBuilder::new(2322u64)
976 .script_gas_limit(10)
977 .coin_input(asset, (amount as Word) * 100)
978 .coin_output(asset, (amount as Word) * 50)
979 .change_output(asset)
980 .build()
981 .transaction()
982 .clone();
983
984 tx.set_expiration(1u32.into());
986 block.transactions_mut().push(tx.clone().into());
987
988 let ExecutionResult {
989 skipped_transactions,
990 mut block,
991 ..
992 } = producer
993 .produce_without_commit(block.into())
994 .unwrap()
995 .into_result();
996
997 assert_eq!(skipped_transactions.len(), 1);
999 assert_eq!(
1000 skipped_transactions[0].1,
1001 ExecutorError::InvalidTransaction(CheckError::Validity(
1002 ValidityError::TransactionExpiration
1003 ))
1004 );
1005
1006 let _ = validator.validate(&block).unwrap().into_result();
1008
1009 let len = block.transactions().len();
1011 block.transactions_mut().insert(len - 1, tx.into());
1012
1013 let verify_error = validator.validate(&block).unwrap_err();
1014 assert_eq!(
1015 verify_error,
1016 ExecutorError::InvalidTransaction(CheckError::Validity(
1017 ValidityError::TransactionExpiration
1018 ))
1019 );
1020 }
1021
1022 #[test]
1024 fn executor_invalidates_missing_gas_input() {
1025 let mut rng = StdRng::seed_from_u64(2322u64);
1026 let consensus_parameters = ConsensusParameters::default();
1027 let config = Config {
1028 consensus_parameters: consensus_parameters.clone(),
1029 ..Default::default()
1030 };
1031 let producer = create_executor(Default::default(), config.clone());
1032
1033 let verifier = create_executor(Default::default(), config);
1034
1035 let gas_limit = 100;
1036 let max_fee = 1;
1037 let script = TransactionBuilder::script(vec![], vec![])
1038 .add_unsigned_coin_input(
1039 SecretKey::random(&mut rng),
1040 rng.r#gen(),
1041 rng.r#gen(),
1042 rng.r#gen(),
1043 Default::default(),
1044 )
1045 .script_gas_limit(gas_limit)
1046 .max_fee_limit(max_fee)
1047 .finalize();
1048 let tx: Transaction = script.into();
1049
1050 let block = PartialFuelBlock {
1051 header: Default::default(),
1052 transactions: vec![tx.clone()],
1053 };
1054
1055 let ExecutionResult {
1056 skipped_transactions,
1057 mut block,
1058 ..
1059 } = producer
1060 .produce_without_commit(block)
1061 .unwrap()
1062 .into_result();
1063 let produce_result = &skipped_transactions[0].1;
1064 assert!(matches!(
1065 produce_result,
1066 &ExecutorError::InvalidTransaction(
1067 CheckError::Validity(
1068 ValidityError::InsufficientFeeAmount { expected, .. }
1069 )
1070 ) if expected == max_fee
1071 ));
1072
1073 let _ = verifier.validate(&block).unwrap().into_result();
1075
1076 let len = block.transactions().len();
1078 block.transactions_mut().insert(len - 1, tx);
1079 let verify_result = verifier.validate(&block);
1080 assert!(matches!(
1081 verify_result,
1082 Err(ExecutorError::InvalidTransaction(
1083 CheckError::Validity(
1084 ValidityError::InsufficientFeeAmount { expected, .. }
1085 )
1086 )) if expected == max_fee
1087 ))
1088 }
1089
1090 #[test]
1091 fn executor_invalidates_duplicate_tx_id() {
1092 let producer = create_executor(Default::default(), Default::default());
1093
1094 let verifier = create_executor(Default::default(), Default::default());
1095
1096 let block = PartialFuelBlock {
1097 header: Default::default(),
1098 transactions: vec![
1099 Transaction::default_test_tx(),
1100 Transaction::default_test_tx(),
1101 ],
1102 };
1103
1104 let ExecutionResult {
1105 skipped_transactions,
1106 mut block,
1107 ..
1108 } = producer
1109 .produce_without_commit(block)
1110 .unwrap()
1111 .into_result();
1112 let produce_result = &skipped_transactions[0].1;
1113 assert!(matches!(
1114 produce_result,
1115 &ExecutorError::TransactionIdCollision(_)
1116 ));
1117
1118 let _ = verifier.validate(&block).unwrap().into_result();
1120
1121 let len = block.transactions().len();
1123 block
1124 .transactions_mut()
1125 .insert(len - 1, Transaction::default_test_tx());
1126 let verify_result = verifier.validate(&block);
1127 assert!(matches!(
1128 verify_result,
1129 Err(ExecutorError::TransactionIdCollision(_))
1130 ));
1131 }
1132
1133 #[test]
1135 fn executor_invalidates_missing_inputs() {
1136 let mut rng = StdRng::seed_from_u64(2322u64);
1138
1139 let tx = TransactionBuilder::script(
1140 vec![op::ret(RegId::ONE)].into_iter().collect(),
1141 vec![],
1142 )
1143 .add_unsigned_coin_input(
1144 SecretKey::random(&mut rng),
1145 rng.r#gen(),
1146 10,
1147 Default::default(),
1148 Default::default(),
1149 )
1150 .add_output(Output::Change {
1151 to: Default::default(),
1152 amount: 0,
1153 asset_id: Default::default(),
1154 })
1155 .finalize_as_transaction();
1156
1157 let config = Config {
1159 forbid_fake_coins_default: true,
1160 ..Default::default()
1161 };
1162 let producer = create_executor(Database::default(), config.clone());
1163
1164 let verifier = create_executor(Default::default(), config);
1165
1166 let block = PartialFuelBlock {
1167 header: Default::default(),
1168 transactions: vec![tx.clone()],
1169 };
1170
1171 let ExecutionResult {
1172 skipped_transactions,
1173 mut block,
1174 ..
1175 } = producer
1176 .produce_without_commit(block)
1177 .unwrap()
1178 .into_result();
1179 let produce_result = &skipped_transactions[0].1;
1180 assert!(matches!(
1181 produce_result,
1182 &ExecutorError::TransactionValidity(
1183 TransactionValidityError::CoinDoesNotExist(_)
1184 )
1185 ));
1186
1187 let _ = verifier.validate(&block).unwrap().into_result();
1189
1190 let len = block.transactions().len();
1192 block.transactions_mut().insert(len - 1, tx);
1193 let verify_result = verifier.validate(&block);
1194 assert!(matches!(
1195 verify_result,
1196 Err(ExecutorError::TransactionValidity(
1197 TransactionValidityError::CoinDoesNotExist(_)
1198 ))
1199 ));
1200 }
1201
1202 #[test]
1205 fn executor_invalidates_blocks_with_diverging_tx_outputs() {
1206 let input_amount = 10;
1207 let fake_output_amount = 100;
1208
1209 let tx: Transaction = TxBuilder::new(2322u64)
1210 .script_gas_limit(1)
1211 .coin_input(Default::default(), input_amount)
1212 .change_output(Default::default())
1213 .build()
1214 .transaction()
1215 .clone()
1216 .into();
1217 let chain_id = ConsensusParameters::default().chain_id();
1218 let transaction_id = tx.id(&chain_id);
1219
1220 let mut producer = create_executor(Default::default(), Default::default());
1221
1222 let mut verifier = create_executor(Default::default(), Default::default());
1223
1224 let mut block = Block::default();
1225 *block.transactions_mut() = vec![tx];
1226
1227 let ExecutionResult { mut block, .. } =
1228 producer.produce_and_commit(block.into()).unwrap();
1229
1230 if let Transaction::Script(script) = &mut block.transactions_mut()[0]
1232 && let Output::Change { amount, .. } = &mut script.outputs_mut()[0]
1233 {
1234 *amount = fake_output_amount
1235 }
1236
1237 let err = verifier.validate_and_commit(&block).unwrap_err();
1239 assert_eq!(
1240 err,
1241 ExecutorError::InvalidTransactionOutcome { transaction_id }
1242 );
1243 }
1244
1245 #[test]
1248 fn executor_invalidates_blocks_with_diverging_tx_commitment() {
1249 let mut rng = StdRng::seed_from_u64(2322u64);
1250 let tx: Transaction = TxBuilder::new(2322u64)
1251 .script_gas_limit(1)
1252 .coin_input(Default::default(), 10)
1253 .change_output(Default::default())
1254 .build()
1255 .transaction()
1256 .clone()
1257 .into();
1258
1259 let mut producer = create_executor(Default::default(), Default::default());
1260
1261 let mut verifier = create_executor(Default::default(), Default::default());
1262
1263 let mut block = Block::default();
1264 *block.transactions_mut() = vec![tx];
1265
1266 let ExecutionResult { mut block, .. } =
1267 producer.produce_and_commit(block.into()).unwrap();
1268
1269 block.header_mut().set_transaction_root(rng.r#gen());
1271 block.header_mut().recalculate_metadata();
1272
1273 let err = verifier.validate_and_commit(&block).unwrap_err();
1274
1275 assert_eq!(err, ExecutorError::BlockMismatch)
1276 }
1277
1278 #[test]
1280 fn executor_invalidates_missing_coin_input() {
1281 let mut tx: Script = Script::default();
1282 tx.policies_mut().set(PolicyType::MaxFee, Some(0));
1283
1284 let mut executor = create_executor(
1285 Database::default(),
1286 Config {
1287 forbid_fake_coins_default: true,
1288 ..Default::default()
1289 },
1290 );
1291
1292 let block = PartialFuelBlock {
1293 header: Default::default(),
1294 transactions: vec![tx.into()],
1295 };
1296
1297 let ExecutionResult {
1298 skipped_transactions,
1299 ..
1300 } = executor.produce_and_commit(block).unwrap();
1301
1302 let err = &skipped_transactions[0].1;
1303 assert!(matches!(
1305 err,
1306 &ExecutorError::InvalidTransaction(CheckError::Validity(
1307 ValidityError::NoSpendableInput
1308 ))
1309 ));
1310 }
1311
1312 #[test]
1313 fn skipped_tx_not_changed_spent_status() {
1314 let tx1 = TxBuilder::new(2322u64)
1320 .coin_input(AssetId::default(), 100)
1321 .change_output(AssetId::default())
1322 .build()
1323 .transaction()
1324 .clone();
1325
1326 let tx2 = TxBuilder::new(2322u64)
1327 .coin_input(AssetId::default(), 100)
1329 .coin_input(AssetId::default(), 100)
1331 .change_output(AssetId::default())
1332 .build()
1333 .transaction()
1334 .clone();
1335
1336 let first_input = tx2.inputs()[0].clone();
1337 let mut first_coin = CompressedCoin::default();
1338 first_coin.set_owner(*first_input.input_owner().unwrap());
1339 first_coin.set_amount(100);
1340 let second_input = tx2.inputs()[1].clone();
1341 let mut second_coin = CompressedCoin::default();
1342 second_coin.set_owner(*second_input.input_owner().unwrap());
1343 second_coin.set_amount(100);
1344 let db = &mut Database::default();
1345 db.storage::<Coins>()
1347 .insert(&first_input.utxo_id().unwrap().clone(), &first_coin)
1348 .unwrap();
1349 db.storage::<Coins>()
1350 .insert(&second_input.utxo_id().unwrap().clone(), &second_coin)
1351 .unwrap();
1352 let mut executor = create_executor(
1353 db.clone(),
1354 Config {
1355 forbid_fake_coins_default: true,
1356 ..Default::default()
1357 },
1358 );
1359
1360 let block = PartialFuelBlock {
1361 header: Default::default(),
1362 transactions: vec![tx1.into(), tx2.clone().into()],
1363 };
1364
1365 db.storage::<Coins>()
1367 .get(first_input.utxo_id().unwrap())
1368 .unwrap()
1369 .expect("coin should be unspent");
1370 db.storage::<Coins>()
1372 .get(second_input.utxo_id().unwrap())
1373 .unwrap()
1374 .expect("coin should be unspent");
1375
1376 let ExecutionResult {
1377 block,
1378 skipped_transactions,
1379 ..
1380 } = executor.produce_and_commit(block).unwrap();
1381 assert_eq!(block.transactions().len(), 2 );
1383 assert_eq!(skipped_transactions.len(), 1);
1384 assert_eq!(skipped_transactions[0].0, tx2.id(&ChainId::default()));
1385
1386 let coin = db
1388 .storage::<Coins>()
1389 .get(first_input.utxo_id().unwrap())
1390 .unwrap();
1391 assert!(coin.is_none());
1393 db.storage::<Coins>()
1395 .get(second_input.utxo_id().unwrap())
1396 .unwrap()
1397 .expect("coin should be unspent");
1398 }
1399
1400 #[test]
1401 fn coin_input_fails_when_mismatches_database() {
1402 const AMOUNT: u64 = 100;
1403
1404 let tx = TxBuilder::new(2322u64)
1405 .coin_input(AssetId::default(), AMOUNT)
1406 .change_output(AssetId::default())
1407 .build()
1408 .transaction()
1409 .clone();
1410
1411 let input = tx.inputs()[0].clone();
1412 let mut coin = CompressedCoin::default();
1413 coin.set_owner(*input.input_owner().unwrap());
1414 coin.set_amount(AMOUNT - 1);
1415 let db = &mut Database::default();
1416
1417 db.storage::<Coins>()
1419 .insert(&input.utxo_id().unwrap().clone(), &coin)
1420 .unwrap();
1421 let mut executor = create_executor(
1422 db.clone(),
1423 Config {
1424 forbid_fake_coins_default: true,
1425 ..Default::default()
1426 },
1427 );
1428
1429 let block = PartialFuelBlock {
1430 header: Default::default(),
1431 transactions: vec![tx.into()],
1432 };
1433
1434 let ExecutionResult {
1435 skipped_transactions,
1436 ..
1437 } = executor.produce_and_commit(block).unwrap();
1438 assert_eq!(skipped_transactions.len(), 1);
1440 let err = &skipped_transactions[0].1;
1441 assert!(matches!(
1442 err,
1443 &ExecutorError::TransactionValidity(TransactionValidityError::CoinMismatch(
1444 _
1445 ))
1446 ));
1447 }
1448
1449 #[test]
1450 fn contract_input_fails_when_doesnt_exist_in_database() {
1451 let contract_id: ContractId = [1; 32].into();
1452 let tx = TxBuilder::new(2322u64)
1453 .contract_input(contract_id)
1454 .coin_input(AssetId::default(), 100)
1455 .change_output(AssetId::default())
1456 .contract_output(&contract_id)
1457 .build()
1458 .transaction()
1459 .clone();
1460
1461 let input = tx.inputs()[1].clone();
1462 let mut coin = CompressedCoin::default();
1463 coin.set_owner(*input.input_owner().unwrap());
1464 coin.set_amount(100);
1465 let db = &mut Database::default();
1466
1467 db.storage::<Coins>()
1468 .insert(&input.utxo_id().unwrap().clone(), &coin)
1469 .unwrap();
1470 let mut executor = create_executor(
1471 db.clone(),
1472 Config {
1473 forbid_fake_coins_default: true,
1474 ..Default::default()
1475 },
1476 );
1477
1478 let block = PartialFuelBlock {
1479 header: Default::default(),
1480 transactions: vec![tx.into()],
1481 };
1482
1483 let ExecutionResult {
1484 skipped_transactions,
1485 ..
1486 } = executor.produce_and_commit(block).unwrap();
1487 assert_eq!(skipped_transactions.len(), 1);
1489 let err = &skipped_transactions[0].1;
1490 assert!(matches!(
1491 err,
1492 &ExecutorError::TransactionValidity(
1493 TransactionValidityError::ContractDoesNotExist(_)
1494 )
1495 ));
1496 }
1497
1498 #[test]
1499 fn transaction_consuming_too_much_gas_are_skipped() {
1500 let mut executor = create_executor(Default::default(), Default::default());
1502 let block: PartialFuelBlock = PartialFuelBlock {
1503 header: Default::default(),
1504 transactions: vec![
1505 TransactionBuilder::script(vec![], vec![])
1506 .max_fee_limit(100_000_000)
1507 .add_fee_input()
1508 .script_gas_limit(0)
1509 .tip(123)
1510 .finalize_as_transaction(),
1511 ],
1512 };
1513
1514 let ExecutionResult { tx_status, .. } =
1516 executor.produce_and_commit(block).unwrap();
1517 let tx_gas_usage = tx_status[0].result.total_gas();
1518
1519 let mut txs = vec![];
1521 for i in 0..10 {
1522 let tx = TransactionBuilder::script(vec![], vec![])
1523 .max_fee_limit(100_000_000)
1524 .add_fee_input()
1525 .script_gas_limit(0)
1526 .tip(i * 100)
1527 .finalize_as_transaction();
1528 txs.push(tx);
1529 }
1530 let mut config: Config = Default::default();
1531 let block_gas_limit = tx_gas_usage * 9;
1533 config
1534 .consensus_parameters
1535 .set_block_gas_limit(block_gas_limit);
1536 let mut executor = create_executor(Default::default(), config);
1537
1538 let block = PartialFuelBlock {
1539 header: Default::default(),
1540 transactions: txs,
1541 };
1542
1543 let ExecutionResult {
1545 skipped_transactions,
1546 ..
1547 } = executor.produce_and_commit(block).unwrap();
1548
1549 assert_eq!(skipped_transactions.len(), 1);
1551 assert_eq!(
1552 skipped_transactions[0].1,
1553 ExecutorError::GasOverflow(
1554 "Transaction cannot fit in remaining gas limit: (0).".into(),
1555 *tx_gas_usage,
1556 0
1557 )
1558 );
1559 }
1560
1561 #[test]
1562 fn skipped_txs_not_affect_order() {
1563 let tx1 = TransactionBuilder::script(vec![], vec![])
1571 .add_fee_input()
1572 .script_gas_limit(1000000)
1573 .tip(1000000)
1574 .finalize_as_transaction();
1575 let (tx2, tx3) = setup_executable_script();
1576
1577 let mut executor = create_executor(Default::default(), Default::default());
1578
1579 let block = PartialFuelBlock {
1580 header: Default::default(),
1581 transactions: vec![tx1.clone(), tx2.clone().into(), tx3.clone().into()],
1582 };
1583
1584 let ExecutionResult {
1585 block,
1586 skipped_transactions,
1587 ..
1588 } = executor.produce_and_commit(block).unwrap();
1589 assert_eq!(
1590 block.transactions().len(),
1591 3 );
1593 assert_eq!(
1594 block.transactions()[0].id(&ChainId::default()),
1595 tx2.id(&ChainId::default())
1596 );
1597 assert_eq!(
1598 block.transactions()[1].id(&ChainId::default()),
1599 tx3.id(&ChainId::default())
1600 );
1601 assert_eq!(skipped_transactions.len(), 1);
1603 assert_eq!(&skipped_transactions[0].0, &tx1.id(&ChainId::default()));
1604 let tx2_index_in_the_block =
1605 block.transactions()[1].as_script().unwrap().inputs()[0]
1606 .tx_pointer()
1607 .unwrap()
1608 .tx_index();
1609 assert_eq!(tx2_index_in_the_block, 0);
1610 }
1611
1612 #[test]
1613 fn input_coins_are_marked_as_spent() {
1614 let tx: Transaction = TxBuilder::new(2322u64)
1616 .coin_input(AssetId::default(), 100)
1617 .change_output(AssetId::default())
1618 .build()
1619 .transaction()
1620 .clone()
1621 .into();
1622
1623 let db = &Database::default();
1624 let mut executor = create_executor(db.clone(), Default::default());
1625
1626 let block = PartialFuelBlock {
1627 header: Default::default(),
1628 transactions: vec![tx],
1629 };
1630
1631 let ExecutionResult { block, .. } = executor.produce_and_commit(block).unwrap();
1632
1633 let coin = db
1635 .storage::<Coins>()
1636 .get(
1637 block.transactions()[0].as_script().unwrap().inputs()[0]
1638 .utxo_id()
1639 .unwrap(),
1640 )
1641 .unwrap();
1642 assert!(coin.is_none());
1644 }
1645
1646 #[test]
1647 fn contracts_balance_and_state_roots_no_modifications_updated() {
1648 let mut rng = StdRng::seed_from_u64(2322u64);
1652
1653 let (create, contract_id) = create_contract(&[], &mut rng);
1654 let non_modify_state_tx: Transaction = TxBuilder::new(2322)
1655 .script_gas_limit(10000)
1656 .coin_input(AssetId::zeroed(), 10000)
1657 .start_script(vec![op::ret(1)], vec![])
1658 .contract_input(contract_id)
1659 .fee_input()
1660 .contract_output(&contract_id)
1661 .build()
1662 .transaction()
1663 .clone()
1664 .into();
1665
1666 let db = &mut Database::default();
1667 let mut executor = create_executor(
1668 db.clone(),
1669 Config {
1670 forbid_fake_coins_default: false,
1671 ..Default::default()
1672 },
1673 );
1674
1675 let block = PartialFuelBlock {
1676 header: PartialBlockHeader {
1677 consensus: ConsensusHeader {
1678 height: 1.into(),
1679 ..Default::default()
1680 },
1681 ..Default::default()
1682 },
1683 transactions: vec![create.into(), non_modify_state_tx],
1684 };
1685
1686 let ExecutionResult {
1687 block, tx_status, ..
1688 } = executor.produce_and_commit(block).unwrap();
1689
1690 assert!(
1692 tx_status
1693 .iter()
1694 .all(|s| matches!(s.result, TransactionExecutionResult::Success { .. }))
1695 );
1696
1697 let executed_tx = block.transactions()[1].as_script().unwrap();
1699 assert_eq!(
1700 executed_tx.inputs()[0].state_root(),
1701 Some(&Bytes32::zeroed())
1702 );
1703 assert_eq!(
1704 executed_tx.inputs()[0].balance_root(),
1705 Some(&Bytes32::zeroed())
1706 );
1707 assert_eq!(
1708 executed_tx.outputs()[0].state_root(),
1709 Some(&Bytes32::zeroed())
1710 );
1711 assert_eq!(
1712 executed_tx.outputs()[0].balance_root(),
1713 Some(&Bytes32::zeroed())
1714 );
1715 }
1716
1717 #[test]
1718 fn contracts_balance_and_state_roots_updated_no_modifications_on_fail() {
1719 let mut rng = StdRng::seed_from_u64(2322u64);
1722
1723 let (create, contract_id) = create_contract(&[], &mut rng);
1724 let non_modify_state_tx: Transaction = TxBuilder::new(2322)
1726 .start_script(vec![op::add(RegId::PC, RegId::PC, RegId::PC)], vec![])
1727 .contract_input(contract_id)
1728 .fee_input()
1729 .contract_output(&contract_id)
1730 .build()
1731 .transaction()
1732 .clone()
1733 .into();
1734
1735 let db = &mut Database::default();
1736 let mut executor = create_executor(
1737 db.clone(),
1738 Config {
1739 forbid_fake_coins_default: false,
1740 ..Default::default()
1741 },
1742 );
1743
1744 let block = PartialFuelBlock {
1745 header: PartialBlockHeader {
1746 consensus: ConsensusHeader {
1747 height: 1.into(),
1748 ..Default::default()
1749 },
1750 ..Default::default()
1751 },
1752 transactions: vec![create.into(), non_modify_state_tx],
1753 };
1754
1755 let ExecutionResult {
1756 block, tx_status, ..
1757 } = executor.produce_and_commit(block).unwrap();
1758
1759 let executed_tx = block.transactions()[1].as_script().unwrap();
1761 assert!(matches!(
1762 tx_status[1].result,
1763 TransactionExecutionResult::Failed { .. }
1764 ));
1765 assert_eq!(
1766 executed_tx.inputs()[0].state_root(),
1767 executed_tx.outputs()[0].state_root()
1768 );
1769 assert_eq!(
1770 executed_tx.inputs()[0].balance_root(),
1771 executed_tx.outputs()[0].balance_root()
1772 );
1773
1774 assert_eq!(
1776 executed_tx.inputs()[0].balance_root(),
1777 Some(&Bytes32::zeroed())
1778 );
1779 assert_eq!(
1780 executed_tx.inputs()[0].state_root(),
1781 Some(&Bytes32::zeroed())
1782 );
1783 }
1784
1785 #[test]
1786 fn contracts_balance_and_state_roots_updated_modifications_updated() {
1787 let mut rng = StdRng::seed_from_u64(2322u64);
1790
1791 let (create, contract_id) = create_contract(
1793 vec![
1795 op::srw(0x10, 0x29, RegId::ZERO),
1796 op::addi(0x10, 0x10, 1),
1797 op::sww(RegId::ZERO, 0x29, 0x10),
1798 op::ret(1),
1799 ]
1800 .into_iter()
1801 .collect::<Vec<u8>>()
1802 .as_slice(),
1803 &mut rng,
1804 );
1805
1806 let transfer_amount = 100 as Word;
1807 let asset_id = AssetId::from([2; 32]);
1808 let (script, _) = script_with_data_offset!(
1809 data_offset,
1810 vec![
1811 op::movi(0x10, data_offset + AssetId::LEN as u32),
1813 op::movi(0x11, data_offset),
1815 op::movi(0x12, transfer_amount as u32),
1817 op::call(0x10, 0x12, 0x11, RegId::CGAS),
1818 op::ret(RegId::ONE),
1819 ],
1820 TxParameters::DEFAULT.tx_offset()
1821 );
1822
1823 let script_data: Vec<u8> = [
1824 asset_id.as_ref(),
1825 Call::new(contract_id, 0, 0).to_bytes().as_ref(),
1826 ]
1827 .into_iter()
1828 .flatten()
1829 .copied()
1830 .collect();
1831
1832 let modify_balance_and_state_tx = TxBuilder::new(2322)
1833 .script_gas_limit(10000)
1834 .coin_input(AssetId::zeroed(), 10000)
1835 .start_script(script, script_data)
1836 .contract_input(contract_id)
1837 .coin_input(asset_id, transfer_amount)
1838 .fee_input()
1839 .contract_output(&contract_id)
1840 .build()
1841 .transaction()
1842 .clone();
1843 let db = Database::default();
1844
1845 let mut executor = create_executor(
1846 db.clone(),
1847 Config {
1848 forbid_fake_coins_default: false,
1849 ..Default::default()
1850 },
1851 );
1852
1853 let block = PartialFuelBlock {
1854 header: PartialBlockHeader {
1855 consensus: ConsensusHeader {
1856 height: 1.into(),
1857 ..Default::default()
1858 },
1859 ..Default::default()
1860 },
1861 transactions: vec![create.into(), modify_balance_and_state_tx.into()],
1862 };
1863
1864 let ExecutionResult {
1865 block, tx_status, ..
1866 } = executor.produce_and_commit(block).unwrap();
1867
1868 assert!(
1869 tx_status
1870 .iter()
1871 .all(|s| matches!(s.result, TransactionExecutionResult::Success { .. }))
1872 );
1873
1874 let executed_tx = block.transactions()[1].as_script().unwrap();
1875 let tx_id = executed_tx.id(&ConsensusParameters::standard().chain_id());
1876
1877 let mut hasher = Sha256::new();
1881 hasher.update(asset_id);
1882 hasher.update([0u8]);
1883 let expected_balance_root: [u8; 32] = hasher.finalize().into();
1884 assert_eq!(
1885 executed_tx.inputs()[0].balance_root(),
1886 Some(&Bytes32::new(expected_balance_root))
1887 );
1888
1889 let mut hasher = Sha256::new();
1891 hasher.update(asset_id);
1892 hasher.update([1u8]);
1893 hasher.update(100u64.to_be_bytes()); let expected_balance_root: [u8; 32] = hasher.finalize().into();
1895 assert_eq!(
1896 executed_tx.outputs()[0].balance_root(),
1897 Some(&Bytes32::new(expected_balance_root))
1898 );
1899
1900 let mut hasher = Sha256::new();
1902 hasher.update(tx_id); hasher.update([0u8]); let expected_state_root: [u8; 32] = hasher.finalize().into();
1905 assert_eq!(
1906 executed_tx.inputs()[0].state_root(),
1907 Some(&Bytes32::new(expected_state_root))
1908 );
1909
1910 let mut hasher = Sha256::new();
1912 hasher.update(tx_id); hasher.update([1u8]); hasher.update(32u64.to_be_bytes()); hasher.update({
1916 let mut value = [0u8; 32];
1917 value[..8].copy_from_slice(&1u64.to_be_bytes()); value
1919 }); let expected_state_root: [u8; 32] = hasher.finalize().into();
1921 assert_eq!(
1922 executed_tx.outputs()[0].state_root(),
1923 Some(&Bytes32::new(expected_state_root))
1924 );
1925 }
1926
1927 #[test]
1928 fn contracts_balance_and_state_roots_are_zero_after_reverted_execution() {
1929 let mut rng = StdRng::seed_from_u64(2322u64);
1932
1933 let (create, contract_id) = create_contract(
1935 vec![
1937 op::srw(0x10, 0x29, RegId::ZERO),
1938 op::addi(0x10, 0x10, 1),
1939 op::sww(RegId::ZERO, 0x29, 0x10),
1940 op::ret(1),
1941 ]
1942 .into_iter()
1943 .collect::<Vec<u8>>()
1944 .as_slice(),
1945 &mut rng,
1946 );
1947
1948 let transfer_amount = 100 as Word;
1949 let asset_id = AssetId::from([2; 32]);
1950 let (script, _) = script_with_data_offset!(
1951 data_offset,
1952 vec![
1953 op::movi(0x10, data_offset + AssetId::LEN as u32),
1955 op::movi(0x11, data_offset),
1957 op::movi(0x12, transfer_amount as u32),
1959 op::call(0x10, 0x12, 0x11, RegId::CGAS),
1960 op::rvrt(RegId::ONE),
1962 ],
1963 TxParameters::DEFAULT.tx_offset()
1964 );
1965
1966 let script_data: Vec<u8> = [
1967 asset_id.as_ref(),
1968 Call::new(contract_id, 0, 0).to_bytes().as_ref(),
1969 ]
1970 .into_iter()
1971 .flatten()
1972 .copied()
1973 .collect();
1974
1975 let modify_balance_and_state_tx = TxBuilder::new(2322)
1976 .script_gas_limit(10000)
1977 .coin_input(AssetId::zeroed(), 10000)
1978 .start_script(script, script_data)
1979 .contract_input(contract_id)
1980 .coin_input(asset_id, transfer_amount)
1981 .fee_input()
1982 .contract_output(&contract_id)
1983 .build()
1984 .transaction()
1985 .clone();
1986 let db = Database::default();
1987
1988 let mut executor = create_executor(
1989 db.clone(),
1990 Config {
1991 forbid_fake_coins_default: false,
1992 ..Default::default()
1993 },
1994 );
1995
1996 let block = PartialFuelBlock {
1997 header: PartialBlockHeader {
1998 consensus: ConsensusHeader {
1999 height: 1.into(),
2000 ..Default::default()
2001 },
2002 ..Default::default()
2003 },
2004 transactions: vec![create.into(), modify_balance_and_state_tx.into()],
2005 };
2006
2007 let ExecutionResult {
2008 block, tx_status, ..
2009 } = executor.produce_and_commit(block).unwrap();
2010
2011 assert!(matches!(
2012 tx_status[1].result,
2013 TransactionExecutionResult::Failed { .. }
2014 ));
2015
2016 let executed_tx = block.transactions()[1].as_script().unwrap();
2017 let tx_id = executed_tx.id(&ConsensusParameters::standard().chain_id());
2018
2019 let mut hasher = Sha256::new();
2023 hasher.update(asset_id);
2024 hasher.update([0u8]);
2025 let expected_balance_root: [u8; 32] = hasher.finalize().into();
2026 assert_eq!(
2027 executed_tx.inputs()[0].balance_root(),
2028 Some(&Bytes32::new(expected_balance_root))
2029 );
2030
2031 assert_eq!(
2033 executed_tx.outputs()[0].balance_root(),
2034 Some(&Bytes32::zeroed())
2035 );
2036
2037 let mut hasher = Sha256::new();
2039 hasher.update(tx_id); hasher.update([0u8]); let expected_state_root: [u8; 32] = hasher.finalize().into();
2042 assert_eq!(
2043 executed_tx.inputs()[0].state_root(),
2044 Some(&Bytes32::new(expected_state_root))
2045 );
2046
2047 assert_eq!(
2049 executed_tx.outputs()[0].state_root(),
2050 Some(&Bytes32::zeroed())
2051 );
2052 }
2053
2054 #[test]
2055 fn contracts_balance_and_state_roots_updated_correctly_with_multiple_modifications() {
2056 let mut rng = StdRng::seed_from_u64(2322u64);
2059
2060 let (create, contract_id) = create_contract(
2062 vec![
2064 op::srw(0x10, 0x29, RegId::ZERO),
2065 op::addi(0x10, 0x10, 1),
2066 op::sww(RegId::ZERO, 0x29, 0x10),
2067 op::ret(1),
2068 ]
2069 .into_iter()
2070 .collect::<Vec<u8>>()
2071 .as_slice(),
2072 &mut rng,
2073 );
2074
2075 let transfer_amount = 100 as Word;
2076 let asset_id = AssetId::from([2; 32]);
2077 let (script, _) = script_with_data_offset!(
2078 data_offset,
2079 vec![
2080 op::movi(0x10, data_offset + AssetId::LEN as u32),
2082 op::movi(0x11, data_offset),
2084 op::movi(0x12, transfer_amount as u32),
2086 op::call(0x10, 0x12, 0x11, RegId::CGAS),
2087 op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS),
2089 op::ret(RegId::ONE),
2090 ],
2091 TxParameters::DEFAULT.tx_offset()
2092 );
2093
2094 let script_data: Vec<u8> = [
2095 asset_id.as_ref(),
2096 Call::new(contract_id, 0, 0).to_bytes().as_ref(),
2097 ]
2098 .into_iter()
2099 .flatten()
2100 .copied()
2101 .collect();
2102
2103 let modify_balance_and_state_tx = TxBuilder::new(2322)
2104 .script_gas_limit(10000)
2105 .coin_input(AssetId::zeroed(), 10000)
2106 .start_script(script, script_data)
2107 .contract_input(contract_id)
2108 .coin_input(asset_id, transfer_amount)
2109 .fee_input()
2110 .contract_output(&contract_id)
2111 .build()
2112 .transaction()
2113 .clone();
2114
2115 let db = Database::default();
2116 let mut executor = create_executor(
2117 db.clone(),
2118 Config {
2119 forbid_fake_coins_default: false,
2120 ..Default::default()
2121 },
2122 );
2123
2124 let block = PartialFuelBlock {
2125 header: PartialBlockHeader {
2126 consensus: ConsensusHeader {
2127 height: 1.into(),
2128 ..Default::default()
2129 },
2130 ..Default::default()
2131 },
2132 transactions: vec![create.into(), modify_balance_and_state_tx.into()],
2133 };
2134
2135 let ExecutionResult {
2136 block, tx_status, ..
2137 } = executor.produce_and_commit(block).unwrap();
2138 assert!(
2139 tx_status
2140 .iter()
2141 .all(|s| matches!(s.result, TransactionExecutionResult::Success { .. }))
2142 );
2143
2144 let executed_tx = block.transactions()[1].as_script().unwrap();
2145 let tx_id = executed_tx.id(&ConsensusParameters::standard().chain_id());
2146
2147 let mut hasher = Sha256::new();
2151 hasher.update(asset_id);
2152 hasher.update([0u8]);
2153 let expected_balance_root: [u8; 32] = hasher.finalize().into();
2154 assert_eq!(
2155 executed_tx.inputs()[0].balance_root(),
2156 Some(&Bytes32::new(expected_balance_root))
2157 );
2158
2159 let mut hasher = Sha256::new();
2161 hasher.update(asset_id);
2162 hasher.update([1u8]);
2163 hasher.update(100u64.to_be_bytes()); let expected_balance_root: [u8; 32] = hasher.finalize().into();
2165 assert_eq!(
2166 executed_tx.outputs()[0].balance_root(),
2167 Some(&Bytes32::new(expected_balance_root))
2168 );
2169
2170 let mut hasher = Sha256::new();
2172 hasher.update(tx_id); hasher.update([0u8]); let expected_state_root: [u8; 32] = hasher.finalize().into();
2175 assert_eq!(
2176 executed_tx.inputs()[0].state_root(),
2177 Some(&Bytes32::new(expected_state_root))
2178 );
2179
2180 let mut hasher = Sha256::new();
2182 hasher.update(tx_id); hasher.update([1u8]); hasher.update(32u64.to_be_bytes()); hasher.update({
2186 let mut value = [0u8; 32];
2187 value[..8].copy_from_slice(&2u64.to_be_bytes()); value
2189 }); let expected_state_root: [u8; 32] = hasher.finalize().into();
2191 assert_eq!(
2192 executed_tx.outputs()[0].state_root(),
2193 Some(&Bytes32::new(expected_state_root))
2194 );
2195 }
2196
2197 #[test]
2198 fn contracts_balance_and_state_roots_updated_correctly_after_modifying_multiple_slots()
2199 {
2200 let mut rng = StdRng::seed_from_u64(2322u64);
2203
2204 let (create, contract_id) = create_contract(
2206 vec![
2208 op::movi(0x10, 32),
2210 op::aloc(0x10),
2211 op::movi(0x10, 0),
2213 op::sww(RegId::HP, 0x29, 0x10),
2214 op::addi(0x10, 0x10, 1),
2215 op::sw(RegId::HP, 0x10, 0),
2216 op::sww(RegId::HP, 0x29, 0x10),
2217 op::addi(0x10, 0x10, 1),
2218 op::sw(RegId::HP, 0x10, 0),
2219 op::sww(RegId::HP, 0x29, 0x10),
2220 op::addi(0x10, 0x10, 1),
2221 op::sw(RegId::HP, 0x10, 0),
2222 op::sww(RegId::HP, 0x29, 0x10),
2223 op::ret(1),
2225 ]
2226 .into_iter()
2227 .collect::<Vec<u8>>()
2228 .as_slice(),
2229 &mut rng,
2230 );
2231
2232 let transfer_amount = 100 as Word;
2233 let asset_id = AssetId::from([2; 32]);
2234 let (script, _) = script_with_data_offset!(
2235 data_offset,
2236 vec![
2237 op::movi(0x10, data_offset + AssetId::LEN as u32),
2239 op::call(0x10, RegId::ZERO, RegId::ZERO, RegId::CGAS),
2240 op::ret(RegId::ONE),
2241 ],
2242 TxParameters::DEFAULT.tx_offset()
2243 );
2244
2245 let script_data: Vec<u8> = [
2246 asset_id.as_ref(),
2247 Call::new(contract_id, 0, 0).to_bytes().as_ref(),
2248 ]
2249 .into_iter()
2250 .flatten()
2251 .copied()
2252 .collect();
2253
2254 let mut builder = TxBuilder::new(2322);
2255
2256 let tx_1 = builder
2257 .script_gas_limit(10000)
2258 .coin_input(AssetId::zeroed(), 10000)
2259 .start_script(script.clone(), script_data.clone())
2260 .contract_input(contract_id)
2261 .coin_input(asset_id, transfer_amount)
2262 .fee_input()
2263 .contract_output(&contract_id)
2264 .build()
2265 .transaction()
2266 .clone();
2267
2268 let tx_2 = builder
2269 .script_gas_limit(10000)
2270 .coin_input(AssetId::zeroed(), 10000)
2271 .start_script(script, script_data)
2272 .contract_input(contract_id)
2273 .coin_input(asset_id, transfer_amount)
2274 .fee_input()
2275 .contract_output(&contract_id)
2276 .build()
2277 .transaction()
2278 .clone();
2279
2280 let db = Database::default();
2281 let mut executor = create_executor(
2282 db.clone(),
2283 Config {
2284 forbid_fake_coins_default: false,
2285 ..Default::default()
2286 },
2287 );
2288
2289 let block = PartialFuelBlock {
2290 header: PartialBlockHeader {
2291 consensus: ConsensusHeader {
2292 height: 1.into(),
2293 ..Default::default()
2294 },
2295 ..Default::default()
2296 },
2297 transactions: vec![create.into(), tx_1.into(), tx_2.into()],
2298 };
2299
2300 let ExecutionResult {
2301 block, tx_status, ..
2302 } = executor.produce_and_commit(block).unwrap();
2303 assert!(matches!(
2304 tx_status[3].result,
2305 TransactionExecutionResult::Success { .. }
2306 ));
2307
2308 let executed_tx_1 = block.transactions()[1].as_script().unwrap();
2309 let executed_tx_2 = block.transactions()[2].as_script().unwrap();
2310
2311 assert_eq!(
2315 executed_tx_1.inputs()[0].balance_root(),
2316 Some(&Bytes32::zeroed())
2317 );
2318 assert_eq!(
2319 executed_tx_1.outputs()[0].balance_root(),
2320 Some(&Bytes32::zeroed())
2321 );
2322 assert_eq!(
2323 executed_tx_2.inputs()[0].balance_root(),
2324 Some(&Bytes32::zeroed())
2325 );
2326 assert_eq!(
2327 executed_tx_2.outputs()[0].balance_root(),
2328 Some(&Bytes32::zeroed())
2329 );
2330
2331 let mut hasher = Sha256::new();
2333 for i in 0..4u64 {
2334 let mut slot_id = [0u8; 32];
2335 slot_id[..8].copy_from_slice(&i.to_be_bytes());
2336 hasher.update(slot_id); hasher.update([0u8]); }
2339 let expected_state_root: [u8; 32] = hasher.finalize().into();
2340 assert_eq!(
2341 executed_tx_1.inputs()[0].state_root(),
2342 Some(&Bytes32::new(expected_state_root))
2343 );
2344
2345 let mut hasher = Sha256::new();
2347 for i in 0..4u64 {
2348 let mut slot_id = [0u8; 32];
2349 slot_id[..8].copy_from_slice(&i.to_be_bytes());
2350 hasher.update(slot_id); hasher.update([1u8]); hasher.update(32u64.to_be_bytes()); hasher.update(slot_id); }
2355 let expected_state_root: [u8; 32] = hasher.finalize().into();
2356 assert_eq!(
2357 executed_tx_1.outputs()[0].state_root(),
2358 Some(&Bytes32::new(expected_state_root))
2359 );
2360 assert_eq!(
2361 executed_tx_2.inputs()[0].state_root(),
2362 Some(&Bytes32::new(expected_state_root))
2363 );
2364 assert_eq!(
2365 executed_tx_2.outputs()[0].state_root(),
2366 Some(&Bytes32::new(expected_state_root))
2367 );
2368 }
2369
2370 #[test]
2375 fn contracts_balance_and_state_roots_updated_correctly_after_calling_multiple_contracts()
2376 {
2377 let mut rng = StdRng::seed_from_u64(2322u64);
2378
2379 let contract_code = vec![
2381 op::srw(0x10, 0x29, RegId::ZERO),
2382 op::addi(0x10, 0x10, 1),
2383 op::sww(RegId::ZERO, 0x29, 0x10),
2384 op::ret(1),
2385 ]
2386 .into_iter()
2387 .collect::<Vec<u8>>();
2388
2389 let (create1, contract_id1) = create_contract(&contract_code, &mut rng);
2391 let (create2, contract_id2) = create_contract(&contract_code, &mut rng);
2392
2393 let transfer_amount = 100 as Word;
2394 let asset_id = AssetId::from([2; 32]);
2395
2396 let mut builder = TxBuilder::new(2322);
2397
2398 let (script1, _) = script_with_data_offset!(
2399 data_offset,
2400 vec![
2401 op::movi(0x11, data_offset),
2403 op::movi(0x12, transfer_amount as u32),
2405 op::movi(0x10, data_offset + AssetId::LEN as u32),
2407 op::call(0x10, 0x12, 0x11, RegId::CGAS),
2408 op::movi(0x10, data_offset + AssetId::LEN as u32 + Call::LEN as u32),
2410 op::call(0x10, 0x12, 0x11, RegId::CGAS),
2411 op::ret(RegId::ONE),
2412 ],
2413 TxParameters::DEFAULT.tx_offset()
2414 );
2415
2416 let script_data1: Vec<u8> = [
2417 asset_id.as_ref(),
2418 Call::new(contract_id1, 0, 0).to_bytes().as_ref(),
2419 Call::new(contract_id2, 0, 0).to_bytes().as_ref(),
2420 ]
2421 .into_iter()
2422 .flatten()
2423 .copied()
2424 .collect();
2425
2426 let tx1 = builder
2427 .script_gas_limit(10000)
2428 .coin_input(AssetId::zeroed(), 10000)
2429 .start_script(script1, script_data1)
2430 .contract_input(contract_id1)
2431 .contract_input(contract_id2)
2432 .coin_input(asset_id, transfer_amount * 2)
2433 .fee_input()
2434 .contract_output(&contract_id1)
2435 .contract_output(&contract_id2)
2436 .build()
2437 .transaction()
2438 .clone();
2439
2440 let (script2, _) = script_with_data_offset!(
2441 data_offset,
2442 vec![
2443 op::movi(0x11, data_offset),
2445 op::movi(0x12, transfer_amount as u32),
2447 op::movi(0x10, data_offset + AssetId::LEN as u32),
2449 op::call(0x10, 0x12, 0x11, RegId::CGAS),
2450 op::ret(RegId::ONE),
2451 ],
2452 TxParameters::DEFAULT.tx_offset()
2453 );
2454
2455 let script_data2: Vec<u8> = [
2456 asset_id.as_ref(),
2457 Call::new(contract_id1, 0, 0).to_bytes().as_ref(),
2458 ]
2459 .into_iter()
2460 .flatten()
2461 .copied()
2462 .collect();
2463
2464 let tx2 = builder
2465 .script_gas_limit(10000)
2466 .coin_input(AssetId::zeroed(), 10000)
2467 .start_script(script2, script_data2)
2468 .contract_input(contract_id1)
2469 .coin_input(asset_id, transfer_amount)
2470 .fee_input()
2471 .contract_output(&contract_id1)
2472 .build()
2473 .transaction()
2474 .clone();
2475
2476 let db = Database::default();
2477 let mut executor = create_executor(
2478 db.clone(),
2479 Config {
2480 forbid_fake_coins_default: false,
2481 ..Default::default()
2482 },
2483 );
2484
2485 let block = PartialFuelBlock {
2486 header: PartialBlockHeader {
2487 consensus: ConsensusHeader {
2488 height: 1.into(),
2489 ..Default::default()
2490 },
2491 ..Default::default()
2492 },
2493 transactions: vec![create1.into(), create2.into(), tx1.into(), tx2.into()],
2494 };
2495
2496 let ExecutionResult {
2497 block, tx_status, ..
2498 } = executor.produce_and_commit(block).unwrap();
2499 assert!(
2500 tx_status
2501 .iter()
2502 .all(|s| matches!(s.result, TransactionExecutionResult::Success { .. }))
2503 );
2504
2505 let executed_tx = block.transactions()[2].as_script().unwrap();
2509 let tx_id1 = executed_tx.id(&ConsensusParameters::standard().chain_id());
2510
2511 let mut contract_ids = [contract_id1, contract_id2];
2512 contract_ids.sort();
2513
2514 let mut hasher = Sha256::new();
2516 hasher.update(asset_id);
2517 hasher.update([0u8]);
2518 let expected_balance_root: [u8; 32] = hasher.finalize().into();
2519 assert_eq!(
2520 executed_tx.inputs()[0].balance_root(),
2521 Some(&Bytes32::new(expected_balance_root))
2522 );
2523 assert_eq!(
2524 executed_tx.inputs()[1].balance_root(),
2525 Some(&Bytes32::new(expected_balance_root))
2526 );
2527
2528 let mut hasher = Sha256::new();
2530 hasher.update(asset_id);
2531 hasher.update([1u8]);
2532 hasher.update(100u64.to_be_bytes()); let expected_balance_root: [u8; 32] = hasher.finalize().into();
2534 assert_eq!(
2535 executed_tx.outputs()[0].balance_root(),
2536 Some(&Bytes32::new(expected_balance_root))
2537 );
2538 assert_eq!(
2539 executed_tx.outputs()[1].balance_root(),
2540 Some(&Bytes32::new(expected_balance_root))
2541 );
2542
2543 let mut hasher = Sha256::new();
2545 hasher.update(tx_id1); hasher.update([0u8]); let expected_state_root: [u8; 32] = hasher.finalize().into();
2548 assert_eq!(
2549 executed_tx.inputs()[0].state_root(),
2550 Some(&Bytes32::new(expected_state_root))
2551 );
2552 assert_eq!(
2553 executed_tx.inputs()[1].state_root(),
2554 Some(&Bytes32::new(expected_state_root))
2555 );
2556
2557 let mut hasher = Sha256::new();
2559 hasher.update(tx_id1); hasher.update([1u8]); hasher.update(32u64.to_be_bytes()); hasher.update({
2563 let mut value = [0u8; 32];
2564 value[..8].copy_from_slice(&1u64.to_be_bytes()); value
2566 });
2567 let expected_state_root: [u8; 32] = hasher.finalize().into();
2568 assert_eq!(
2569 executed_tx.outputs()[0].state_root(),
2570 Some(&Bytes32::new(expected_state_root))
2571 );
2572 assert_eq!(
2573 executed_tx.outputs()[1].state_root(),
2574 Some(&Bytes32::new(expected_state_root))
2575 );
2576
2577 let executed_tx = block.transactions()[3].as_script().unwrap();
2579 let tx_id2 = executed_tx.id(&ConsensusParameters::standard().chain_id());
2580
2581 let mut tx_ids = [tx_id1, tx_id2];
2582 tx_ids.sort();
2583
2584 let mut hasher = Sha256::new();
2586 hasher.update(asset_id);
2587 hasher.update([1u8]);
2588 hasher.update(100u64.to_be_bytes()); let expected_balance_root: [u8; 32] = hasher.finalize().into();
2590 assert_eq!(
2591 executed_tx.inputs()[0].balance_root(),
2592 Some(&Bytes32::new(expected_balance_root))
2593 );
2594
2595 let mut hasher = Sha256::new();
2597 hasher.update(asset_id);
2598 hasher.update([1u8]);
2599 hasher.update(200u64.to_be_bytes()); let expected_balance_root: [u8; 32] = hasher.finalize().into();
2601 assert_eq!(
2602 executed_tx.outputs()[0].balance_root(),
2603 Some(&Bytes32::new(expected_balance_root))
2604 );
2605
2606 let mut hasher = Sha256::new();
2608 hasher.update(tx_id2); hasher.update([0u8]); let expected_state_root: [u8; 32] = hasher.finalize().into();
2611 assert_eq!(
2612 executed_tx.inputs()[0].state_root(),
2613 Some(&Bytes32::new(expected_state_root))
2614 );
2615
2616 let mut hasher = Sha256::new();
2618 hasher.update(tx_id2); hasher.update([1u8]); hasher.update(32u64.to_be_bytes()); hasher.update({
2622 let mut value = [0u8; 32];
2623 value[..8].copy_from_slice(&1u64.to_be_bytes()); value
2625 });
2626 let expected_state_root: [u8; 32] = hasher.finalize().into();
2627 assert_eq!(
2628 executed_tx.outputs()[0].state_root(),
2629 Some(&Bytes32::new(expected_state_root))
2630 );
2631 }
2632
2633 #[test]
2634 fn foreign_transfer_should_not_affect_balance_root() {
2635 let mut rng = StdRng::seed_from_u64(2322u64);
2637
2638 let (create, contract_id) = create_contract(&[], &mut rng);
2639
2640 let transfer_amount = 100 as Word;
2641 let asset_id = AssetId::from([2; 32]);
2642 let mut foreign_transfer = TxBuilder::new(2322)
2643 .script_gas_limit(10000)
2644 .coin_input(AssetId::zeroed(), 10000)
2645 .start_script(vec![op::ret(1)], vec![])
2646 .coin_input(asset_id, transfer_amount)
2647 .coin_output(asset_id, transfer_amount)
2648 .build()
2649 .transaction()
2650 .clone();
2651 if let Some(Output::Coin { to, .. }) = foreign_transfer
2652 .as_script_mut()
2653 .unwrap()
2654 .outputs_mut()
2655 .last_mut()
2656 {
2657 *to = Address::try_from(contract_id.as_ref()).unwrap();
2658 } else {
2659 panic!("Last outputs should be a coin for the contract");
2660 }
2661 let db = &mut Database::default();
2662
2663 let mut executor = create_executor(db.clone(), Default::default());
2664
2665 let block = PartialFuelBlock {
2666 header: PartialBlockHeader {
2667 consensus: ConsensusHeader {
2668 height: 1.into(),
2669 ..Default::default()
2670 },
2671 ..Default::default()
2672 },
2673 transactions: vec![create.into(), foreign_transfer.into()],
2674 };
2675
2676 let _ = executor.produce_and_commit(block).unwrap();
2677
2678 assert_eq!(
2680 ContractRef::new(db, contract_id).balance_root().unwrap(),
2681 Bytes32::zeroed()
2682 );
2683 }
2684
2685 #[test]
2686 fn input_coins_are_marked_as_spent_with_utxo_validation_enabled() {
2687 let mut rng = StdRng::seed_from_u64(2322u64);
2689 let starting_block = BlockHeight::from(5);
2690 let starting_block_tx_idx = Default::default();
2691
2692 let tx = TransactionBuilder::script(
2693 vec![op::ret(RegId::ONE)].into_iter().collect(),
2694 vec![],
2695 )
2696 .add_unsigned_coin_input(
2697 SecretKey::random(&mut rng),
2698 rng.r#gen(),
2699 100,
2700 Default::default(),
2701 Default::default(),
2702 )
2703 .add_output(Output::Change {
2704 to: Default::default(),
2705 amount: 0,
2706 asset_id: Default::default(),
2707 })
2708 .finalize();
2709 let db = &mut Database::default();
2710
2711 if let Input::CoinSigned(CoinSigned {
2713 utxo_id,
2714 owner,
2715 amount,
2716 asset_id,
2717 ..
2718 }) = tx.inputs()[0]
2719 {
2720 let mut coin = CompressedCoin::default();
2721 coin.set_owner(owner);
2722 coin.set_amount(amount);
2723 coin.set_asset_id(asset_id);
2724 coin.set_tx_pointer(TxPointer::new(starting_block, starting_block_tx_idx));
2725 db.storage::<Coins>().insert(&utxo_id, &coin).unwrap();
2726 }
2727
2728 let mut executor = create_executor(
2729 db.clone(),
2730 Config {
2731 forbid_fake_coins_default: true,
2732 ..Default::default()
2733 },
2734 );
2735
2736 let block = PartialFuelBlock {
2737 header: PartialBlockHeader {
2738 consensus: ConsensusHeader {
2739 height: 6.into(),
2740 ..Default::default()
2741 },
2742 ..Default::default()
2743 },
2744 transactions: vec![tx.into()],
2745 };
2746
2747 let ExecutionResult { block, events, .. } =
2748 executor.produce_and_commit(block).unwrap();
2749
2750 let utxo_id = block.transactions()[0].as_script().unwrap().inputs()[0]
2752 .utxo_id()
2753 .unwrap();
2754 let coin = db.storage::<Coins>().get(utxo_id).unwrap();
2755 assert!(coin.is_none());
2756 assert_eq!(events.len(), 2);
2757 assert!(
2758 matches!(events[0], ExecutorEvent::CoinConsumed(spent_coin) if &spent_coin.utxo_id == utxo_id)
2759 );
2760 assert!(matches!(events[1], ExecutorEvent::CoinCreated(_)));
2761 }
2762
2763 #[test]
2764 fn validation_succeeds_when_input_contract_utxo_id_uses_expected_value() {
2765 let mut rng = StdRng::seed_from_u64(2322);
2766 let (tx, contract_id) = create_contract(&[], &mut rng);
2769 let first_block = PartialFuelBlock {
2770 header: Default::default(),
2771 transactions: vec![tx.into()],
2772 };
2773
2774 let tx2: Transaction = TxBuilder::new(2322)
2775 .start_script(vec![op::ret(1)], vec![])
2776 .contract_input(contract_id)
2777 .fee_input()
2778 .contract_output(&contract_id)
2779 .build()
2780 .transaction()
2781 .clone()
2782 .into();
2783
2784 let second_block = PartialFuelBlock {
2785 header: PartialBlockHeader {
2786 consensus: ConsensusHeader {
2787 height: 2.into(),
2788 ..Default::default()
2789 },
2790 ..Default::default()
2791 },
2792 transactions: vec![tx2],
2793 };
2794
2795 let db = Database::default();
2796
2797 let mut setup = create_executor(db.clone(), Default::default());
2798
2799 let ExecutionResult {
2800 skipped_transactions,
2801 ..
2802 } = setup.produce_and_commit(first_block).unwrap();
2803 assert!(skipped_transactions.is_empty());
2804
2805 let producer = create_executor(db.clone(), Default::default());
2806 let ExecutionResult {
2807 block: second_block,
2808 skipped_transactions,
2809 ..
2810 } = producer
2811 .produce_without_commit(second_block)
2812 .unwrap()
2813 .into_result();
2814 assert!(skipped_transactions.is_empty());
2815
2816 let verifier = create_executor(db, Default::default());
2817 let verify_result = verifier.validate(&second_block);
2818 assert!(verify_result.is_ok());
2819 }
2820
2821 #[test]
2823 fn invalidates_if_input_contract_utxo_id_is_divergent() {
2824 let mut rng = StdRng::seed_from_u64(2322);
2825
2826 let (tx, contract_id) = create_contract(&[], &mut rng);
2829 let tx2: Transaction = TxBuilder::new(2322)
2830 .start_script(vec![op::addi(0x10, RegId::ZERO, 0), op::ret(1)], vec![])
2831 .contract_input(contract_id)
2832 .fee_input()
2833 .contract_output(&contract_id)
2834 .build()
2835 .transaction()
2836 .clone()
2837 .into();
2838
2839 let first_block = PartialFuelBlock {
2840 header: Default::default(),
2841 transactions: vec![tx.into(), tx2],
2842 };
2843
2844 let tx3: Transaction = TxBuilder::new(2322)
2845 .start_script(vec![op::addi(0x10, RegId::ZERO, 1), op::ret(1)], vec![])
2846 .contract_input(contract_id)
2847 .fee_input()
2848 .contract_output(&contract_id)
2849 .build()
2850 .transaction()
2851 .clone()
2852 .into();
2853 let tx_id = tx3.id(&ChainId::default());
2854
2855 let second_block = PartialFuelBlock {
2856 header: PartialBlockHeader {
2857 consensus: ConsensusHeader {
2858 height: 2.into(),
2859 ..Default::default()
2860 },
2861 ..Default::default()
2862 },
2863 transactions: vec![tx3],
2864 };
2865
2866 let db = Database::default();
2867
2868 let mut setup = create_executor(db.clone(), Default::default());
2869
2870 setup.produce_and_commit(first_block).unwrap();
2871
2872 let producer = create_executor(db.clone(), Default::default());
2873
2874 let ExecutionResult {
2875 block: mut second_block,
2876 ..
2877 } = producer
2878 .produce_without_commit(second_block)
2879 .unwrap()
2880 .into_result();
2881 if let Transaction::Script(script) = &mut second_block.transactions_mut()[0]
2883 && let Input::Contract(contract::Contract { utxo_id, .. }) =
2884 &mut script.inputs_mut()[0]
2885 {
2886 *utxo_id = UtxoId::new(tx_id, 0);
2888 }
2889
2890 let verifier = create_executor(db, Default::default());
2891 let err = verifier.validate(&second_block).unwrap_err();
2892
2893 assert_eq!(
2894 err,
2895 ExecutorError::InvalidTransactionOutcome {
2896 transaction_id: tx_id
2897 }
2898 );
2899 }
2900
2901 #[test]
2902 fn outputs_with_amount_are_included_utxo_set() {
2903 let (deploy, script) = setup_executable_script();
2904 let script_id = script.id(&ChainId::default());
2905
2906 let database = &Database::default();
2907 let mut executor = create_executor(database.clone(), Default::default());
2908
2909 let block = PartialFuelBlock {
2910 header: Default::default(),
2911 transactions: vec![deploy.into(), script.into()],
2912 };
2913
2914 let ExecutionResult { block, .. } = executor.produce_and_commit(block).unwrap();
2915
2916 for (idx, output) in block.transactions()[1]
2918 .as_script()
2919 .unwrap()
2920 .outputs()
2921 .iter()
2922 .enumerate()
2923 {
2924 let id = UtxoId::new(script_id, idx as u16);
2925 match output {
2926 Output::Change { .. } | Output::Variable { .. } | Output::Coin { .. } => {
2927 let maybe_utxo = database.storage::<Coins>().get(&id).unwrap();
2928 assert!(maybe_utxo.is_some());
2929 let utxo = maybe_utxo.unwrap();
2930 assert!(*utxo.amount() > 0)
2931 }
2932 _ => (),
2933 }
2934 }
2935 }
2936
2937 #[test]
2938 fn outputs_with_no_value_are_excluded_from_utxo_set() {
2939 let mut rng = StdRng::seed_from_u64(2322);
2940 let asset_id: AssetId = rng.r#gen();
2941 let input_amount = 0;
2942 let coin_output_amount = 0;
2943
2944 let tx: Transaction = TxBuilder::new(2322)
2945 .coin_input(asset_id, input_amount)
2946 .variable_output(Default::default())
2947 .coin_output(asset_id, coin_output_amount)
2948 .change_output(asset_id)
2949 .build()
2950 .transaction()
2951 .clone()
2952 .into();
2953 let tx_id = tx.id(&ChainId::default());
2954
2955 let database = &Database::default();
2956 let mut executor = create_executor(database.clone(), Default::default());
2957
2958 let block = PartialFuelBlock {
2959 header: Default::default(),
2960 transactions: vec![tx],
2961 };
2962
2963 executor.produce_and_commit(block).unwrap();
2964
2965 for idx in 0..2 {
2966 let id = UtxoId::new(tx_id, idx);
2967 let maybe_utxo = database.storage::<Coins>().get(&id).unwrap();
2968 assert!(maybe_utxo.is_none());
2969 }
2970 }
2971
2972 fn message_from_input(input: &Input, da_height: u64) -> Message {
2973 MessageV1 {
2974 sender: *input.sender().unwrap(),
2975 recipient: *input.recipient().unwrap(),
2976 nonce: *input.nonce().unwrap(),
2977 amount: input.amount().unwrap(),
2978 data: input
2979 .input_data()
2980 .map(|data| data.to_vec())
2981 .unwrap_or_default(),
2982 da_height: DaBlockHeight(da_height),
2983 }
2984 .into()
2985 }
2986
2987 fn make_tx_and_message(rng: &mut StdRng, da_height: u64) -> (Transaction, Message) {
2989 let tx = TransactionBuilder::script(vec![], vec![])
2990 .add_unsigned_message_input(
2991 SecretKey::random(rng),
2992 rng.r#gen(),
2993 rng.r#gen(),
2994 1000,
2995 vec![],
2996 )
2997 .add_output(Output::change(rng.r#gen(), 1000, AssetId::BASE))
2998 .finalize();
2999
3000 let message = message_from_input(&tx.inputs()[0], da_height);
3001 (tx.into(), message)
3002 }
3003
3004 fn make_executor(messages: &[&Message]) -> Executor<Database, DisabledRelayer> {
3006 let mut database = Database::default();
3007 let database_ref = &mut database;
3008
3009 for message in messages {
3010 database_ref
3011 .storage::<Messages>()
3012 .insert(message.id(), message)
3013 .unwrap();
3014 }
3015
3016 create_executor(
3017 database,
3018 Config {
3019 forbid_fake_coins_default: true,
3020 ..Default::default()
3021 },
3022 )
3023 }
3024
3025 #[test]
3026 fn unspent_message_succeeds_when_msg_da_height_lt_block_da_height() {
3027 let mut rng = StdRng::seed_from_u64(2322);
3028
3029 let (tx, message) = make_tx_and_message(&mut rng, 0);
3030
3031 let block = PartialFuelBlock {
3032 header: Default::default(),
3033 transactions: vec![tx],
3034 };
3035
3036 let ExecutionResult { block, .. } = make_executor(&[&message])
3037 .produce_and_commit(block)
3038 .expect("block execution failed unexpectedly");
3039
3040 make_executor(&[&message])
3041 .validate_and_commit(&block)
3042 .expect("block validation failed unexpectedly");
3043 }
3044
3045 #[test]
3046 fn successful_execution_consume_all_messages() {
3047 let mut rng = StdRng::seed_from_u64(2322);
3048 let to: Address = rng.r#gen();
3049 let amount = 500;
3050
3051 let tx = TransactionBuilder::script(vec![], vec![])
3052 .add_unsigned_message_input(SecretKey::random(&mut rng), rng.r#gen(), rng.r#gen(), amount, vec![])
3054 .add_unsigned_message_input(SecretKey::random(&mut rng), rng.r#gen(), rng.r#gen(), amount, vec![0xff; 10])
3056 .add_output(Output::change(to, amount + amount, AssetId::BASE))
3057 .finalize();
3058 let tx_id = tx.id(&ChainId::default());
3059
3060 let message_coin = message_from_input(&tx.inputs()[0], 0);
3061 let message_data = message_from_input(&tx.inputs()[1], 0);
3062 let messages = vec![&message_coin, &message_data];
3063
3064 let block = PartialFuelBlock {
3065 header: Default::default(),
3066 transactions: vec![tx.into()],
3067 };
3068
3069 let mut exec = make_executor(&messages);
3070 let view = exec.storage_view_provider.latest_view().unwrap();
3071 assert!(view.message_exists(message_coin.nonce()).unwrap());
3072 assert!(view.message_exists(message_data.nonce()).unwrap());
3073
3074 let ExecutionResult {
3075 skipped_transactions,
3076 ..
3077 } = exec.produce_and_commit(block).unwrap();
3078 assert_eq!(skipped_transactions.len(), 0);
3079
3080 let view = exec.storage_view_provider.latest_view().unwrap();
3082 assert!(!view.message_exists(message_coin.nonce()).unwrap());
3083 assert!(!view.message_exists(message_data.nonce()).unwrap());
3084 assert_eq!(
3085 *view.coin(&UtxoId::new(tx_id, 0)).unwrap().amount(),
3086 amount + amount
3087 );
3088 }
3089
3090 #[test]
3091 fn reverted_execution_consume_only_message_coins() {
3092 let mut rng = StdRng::seed_from_u64(2322);
3093 let to: Address = rng.r#gen();
3094 let amount = 500;
3095
3096 let script = vec![op::ret(1)].into_iter().collect();
3098 let tx = TransactionBuilder::script(script, vec![])
3099 .add_unsigned_message_input(SecretKey::random(&mut rng), rng.r#gen(), rng.r#gen(), amount, vec![])
3101 .add_unsigned_message_input(SecretKey::random(&mut rng), rng.r#gen(), rng.r#gen(), amount, vec![0xff; 10])
3103 .add_output(Output::change(to, amount + amount, AssetId::BASE))
3104 .finalize();
3105 let tx_id = tx.id(&ChainId::default());
3106
3107 let message_coin = message_from_input(&tx.inputs()[0], 0);
3108 let message_data = message_from_input(&tx.inputs()[1], 0);
3109 let messages = vec![&message_coin, &message_data];
3110
3111 let block = PartialFuelBlock {
3112 header: Default::default(),
3113 transactions: vec![tx.into()],
3114 };
3115
3116 let mut exec = make_executor(&messages);
3117 let view = exec.storage_view_provider.latest_view().unwrap();
3118 assert!(view.message_exists(message_coin.nonce()).unwrap());
3119 assert!(view.message_exists(message_data.nonce()).unwrap());
3120
3121 let ExecutionResult {
3122 skipped_transactions,
3123 ..
3124 } = exec.produce_and_commit(block).unwrap();
3125 assert_eq!(skipped_transactions.len(), 0);
3126
3127 let view = exec.storage_view_provider.latest_view().unwrap();
3129 assert!(!view.message_exists(message_coin.nonce()).unwrap());
3130 assert!(view.message_exists(message_data.nonce()).unwrap());
3131 assert_eq!(*view.coin(&UtxoId::new(tx_id, 0)).unwrap().amount(), amount);
3132 }
3133
3134 #[test]
3135 fn message_fails_when_spending_nonexistent_message_id() {
3136 let mut rng = StdRng::seed_from_u64(2322);
3137
3138 let (tx, _message) = make_tx_and_message(&mut rng, 0);
3139
3140 let mut block = Block::default();
3141 *block.transactions_mut() = vec![tx.clone()];
3142
3143 let ExecutionResult {
3144 skipped_transactions,
3145 mut block,
3146 ..
3147 } = make_executor(&[]) .produce_and_commit(block.clone().into())
3149 .unwrap();
3150 let err = &skipped_transactions[0].1;
3151 assert!(matches!(
3152 err,
3153 &ExecutorError::TransactionValidity(
3154 TransactionValidityError::MessageDoesNotExist(_)
3155 )
3156 ));
3157
3158 make_executor(&[]) .validate_and_commit(&block)
3161 .unwrap();
3162
3163 let index = block.transactions().len() - 1;
3165 block.transactions_mut().insert(index, tx);
3166 let res = make_executor(&[]) .validate_and_commit(&block);
3168 assert!(matches!(
3169 res,
3170 Err(ExecutorError::TransactionValidity(
3171 TransactionValidityError::MessageDoesNotExist(_)
3172 ))
3173 ));
3174 }
3175
3176 #[test]
3177 fn message_fails_when_spending_da_height_gt_block_da_height() {
3178 let mut rng = StdRng::seed_from_u64(2322);
3179
3180 let (tx, message) = make_tx_and_message(&mut rng, 1); let mut block = Block::default();
3183 *block.transactions_mut() = vec![tx.clone()];
3184
3185 let ExecutionResult {
3186 skipped_transactions,
3187 mut block,
3188 ..
3189 } = make_executor(&[&message])
3190 .produce_and_commit(block.clone().into())
3191 .unwrap();
3192 let err = &skipped_transactions[0].1;
3193 assert!(matches!(
3194 err,
3195 &ExecutorError::TransactionValidity(
3196 TransactionValidityError::MessageSpendTooEarly(_)
3197 )
3198 ));
3199
3200 make_executor(&[&message])
3202 .validate_and_commit(&block)
3203 .unwrap();
3204
3205 let index = block.transactions().len() - 1;
3207 block.transactions_mut().insert(index, tx);
3208 let res = make_executor(&[&message]).validate_and_commit(&block);
3209 assert!(matches!(
3210 res,
3211 Err(ExecutorError::TransactionValidity(
3212 TransactionValidityError::MessageSpendTooEarly(_)
3213 ))
3214 ));
3215 }
3216
3217 #[test]
3218 fn message_input_fails_when_mismatches_database() {
3219 let mut rng = StdRng::seed_from_u64(2322);
3220
3221 let (tx, mut message) = make_tx_and_message(&mut rng, 0);
3222
3223 message.set_amount(123);
3225
3226 let mut block = Block::default();
3227 *block.transactions_mut() = vec![tx.clone()];
3228
3229 let ExecutionResult {
3230 skipped_transactions,
3231 ..
3232 } = make_executor(&[&message])
3233 .produce_and_commit(block.clone().into())
3234 .unwrap();
3235 let err = &skipped_transactions[0].1;
3236 assert!(matches!(
3237 err,
3238 &ExecutorError::TransactionValidity(
3239 TransactionValidityError::MessageMismatch(_)
3240 )
3241 ));
3242 }
3243
3244 #[test]
3245 fn message_fails_when_spending_already_spent_message_id() {
3246 let mut rng = StdRng::seed_from_u64(2322);
3247
3248 let (tx1, message) = make_tx_and_message(&mut rng, 0);
3250 let (mut tx2, _) = make_tx_and_message(&mut rng, 0);
3251 tx2.as_script_mut().unwrap().inputs_mut()[0] =
3252 tx1.as_script().unwrap().inputs()[0].clone();
3253
3254 let block = PartialFuelBlock {
3255 header: Default::default(),
3256 transactions: vec![tx1, tx2.clone()],
3257 };
3258
3259 let exec = make_executor(&[&message]);
3260 let ExecutionResult {
3261 skipped_transactions,
3262 mut block,
3263 ..
3264 } = exec.produce_without_commit(block).unwrap().into_result();
3265 assert_eq!(skipped_transactions.len(), 1);
3267 let err = &skipped_transactions[0].1;
3268 assert!(matches!(
3269 err,
3270 &ExecutorError::TransactionValidity(
3271 TransactionValidityError::MessageDoesNotExist(_)
3272 )
3273 ));
3274
3275 let exec = make_executor(&[&message]);
3277 let _ = exec.validate(&block).unwrap().into_result();
3278
3279 let len = block.transactions().len();
3281 block.transactions_mut().insert(len - 1, tx2);
3282 let exec = make_executor(&[&message]);
3283 let res = exec.validate(&block);
3284 assert!(matches!(
3285 res,
3286 Err(ExecutorError::TransactionValidity(
3287 TransactionValidityError::MessageDoesNotExist(_)
3288 ))
3289 ));
3290 }
3291
3292 #[test]
3293 fn withdrawal_message_included_in_header_for_successfully_executed_transaction() {
3294 let amount_from_random_input = 1000;
3296 let smo_tx = TransactionBuilder::script(
3297 vec![
3298 op::movi(0x13, amount_from_random_input),
3300 op::smo(0x0, 0x0, 0x0, 0x13),
3302 op::ret(RegId::ONE),
3303 ]
3304 .into_iter()
3305 .collect(),
3306 vec![],
3307 )
3308 .add_fee_input()
3309 .script_gas_limit(1000000)
3310 .finalize_as_transaction();
3311
3312 let block = PartialFuelBlock {
3313 header: Default::default(),
3314 transactions: vec![smo_tx],
3315 };
3316
3317 let ExecutionResult { block, .. } =
3319 create_executor(Default::default(), Default::default())
3320 .produce_and_commit(block)
3321 .expect("block execution failed unexpectedly");
3322 let result = create_executor(Default::default(), Default::default())
3323 .validate_and_commit(&block)
3324 .expect("block validation failed unexpectedly");
3325
3326 let Some(Receipt::MessageOut {
3328 sender,
3329 recipient,
3330 amount,
3331 nonce,
3332 data,
3333 ..
3334 }) = result.tx_status[0].result.receipts().first().cloned()
3335 else {
3336 panic!("Expected a MessageOut receipt");
3337 };
3338
3339 let mut mt = fuel_core_types::fuel_merkle::binary::in_memory::MerkleTree::new();
3341 mt.push(
3342 &Message::V1(MessageV1 {
3343 sender,
3344 recipient,
3345 nonce,
3346 amount,
3347 data: data.unwrap_or_default().into(),
3348 da_height: 1u64.into(),
3349 })
3350 .message_id()
3351 .to_bytes(),
3352 );
3353 assert_eq!(block.header().message_outbox_root().as_ref(), mt.root());
3354 }
3355
3356 #[test]
3357 fn withdrawal_message_not_included_in_header_for_failed_transaction() {
3358 let amount_from_random_input = 1000;
3360 let smo_tx = TransactionBuilder::script(
3361 vec![
3362 op::movi(0x13, amount_from_random_input),
3364 op::smo(0x0, 0x0, 0x0, 0x13),
3366 op::rvrt(0x0),
3367 ]
3368 .into_iter()
3369 .collect(),
3370 vec![],
3371 )
3372 .add_fee_input()
3373 .script_gas_limit(1000000)
3374 .finalize_as_transaction();
3375
3376 let block = PartialFuelBlock {
3377 header: Default::default(),
3378 transactions: vec![smo_tx],
3379 };
3380
3381 let ExecutionResult { block, .. } =
3383 create_executor(Default::default(), Default::default())
3384 .produce_and_commit(block)
3385 .expect("block execution failed unexpectedly");
3386 create_executor(Default::default(), Default::default())
3387 .validate_and_commit(&block)
3388 .expect("block validation failed unexpectedly");
3389
3390 let empty_root = empty_sum_sha256();
3392 assert_eq!(block.header().message_outbox_root().as_ref(), empty_root)
3393 }
3394
3395 #[test]
3396 fn get_block_height_returns_current_executing_block() {
3397 let mut rng = StdRng::seed_from_u64(1234);
3398
3399 let base_asset_id = rng.r#gen();
3400
3401 let script = vec![op::bhei(0x10), op::ret(0x10)];
3403 let tx = TransactionBuilder::script(script.into_iter().collect(), vec![])
3404 .script_gas_limit(10000)
3405 .add_unsigned_coin_input(
3406 SecretKey::random(&mut rng),
3407 rng.r#gen(),
3408 1000,
3409 base_asset_id,
3410 Default::default(),
3411 )
3412 .finalize();
3413
3414 let block_height = rng.gen_range(5u32..1000u32);
3416 let block_tx_idx = rng.r#gen();
3417
3418 let block = PartialFuelBlock {
3419 header: PartialBlockHeader {
3420 consensus: ConsensusHeader {
3421 height: block_height.into(),
3422 ..Default::default()
3423 },
3424 ..Default::default()
3425 },
3426 transactions: vec![tx.clone().into()],
3427 };
3428
3429 let database = &mut &mut Database::default();
3431 let coin_input = &tx.inputs()[0];
3432 let mut coin = CompressedCoin::default();
3433 coin.set_owner(*coin_input.input_owner().unwrap());
3434 coin.set_amount(coin_input.amount().unwrap());
3435 coin.set_asset_id(*coin_input.asset_id(&base_asset_id).unwrap());
3436 coin.set_tx_pointer(TxPointer::new(Default::default(), block_tx_idx));
3437 database
3438 .storage::<Coins>()
3439 .insert(coin_input.utxo_id().unwrap(), &coin)
3440 .unwrap();
3441
3442 let mut executor = create_executor(
3444 database.clone(),
3445 Config {
3446 forbid_fake_coins_default: true,
3447 ..Default::default()
3448 },
3449 );
3450
3451 let ExecutionResult { tx_status, .. } = executor
3452 .produce_and_commit(block)
3453 .expect("Should execute the block");
3454
3455 let receipts = tx_status[0].result.receipts();
3456 assert_eq!(block_height as u64, receipts[0].val().unwrap());
3457 }
3458
3459 #[test]
3460 fn get_time_returns_current_executing_block_time() {
3461 let mut rng = StdRng::seed_from_u64(1234);
3462
3463 let base_asset_id = rng.r#gen();
3464
3465 let script = vec![op::bhei(0x10), op::time(0x11, 0x10), op::ret(0x11)];
3467 let tx = TransactionBuilder::script(script.into_iter().collect(), vec![])
3468 .script_gas_limit(10000)
3469 .add_unsigned_coin_input(
3470 SecretKey::random(&mut rng),
3471 rng.r#gen(),
3472 1000,
3473 base_asset_id,
3474 Default::default(),
3475 )
3476 .finalize();
3477
3478 let block_height = rng.gen_range(5u32..1000u32);
3480 let time = Tai64(rng.gen_range(1u32..u32::MAX) as u64);
3481
3482 let block = PartialFuelBlock {
3483 header: PartialBlockHeader {
3484 consensus: ConsensusHeader {
3485 height: block_height.into(),
3486 time,
3487 ..Default::default()
3488 },
3489 ..Default::default()
3490 },
3491 transactions: vec![tx.clone().into()],
3492 };
3493
3494 let database = &mut &mut Database::default();
3496 let coin_input = &tx.inputs()[0];
3497 let mut coin = CompressedCoin::default();
3498 coin.set_owner(*coin_input.input_owner().unwrap());
3499 coin.set_amount(coin_input.amount().unwrap());
3500 coin.set_asset_id(*coin_input.asset_id(&base_asset_id).unwrap());
3501 database
3502 .storage::<Coins>()
3503 .insert(coin_input.utxo_id().unwrap(), &coin)
3504 .unwrap();
3505
3506 let mut executor = create_executor(
3508 database.clone(),
3509 Config {
3510 forbid_fake_coins_default: true,
3511 ..Default::default()
3512 },
3513 );
3514
3515 let ExecutionResult { tx_status, .. } = executor
3516 .produce_and_commit(block)
3517 .expect("Should execute the block");
3518
3519 let receipts = tx_status[0].result.receipts();
3520 assert_eq!(time.0, receipts[0].val().unwrap());
3521 }
3522
3523 #[test]
3524 fn tx_with_coin_predicate_included_by_block_producer_and_accepted_by_validator() {
3525 let mut rng = StdRng::seed_from_u64(2322u64);
3526 let predicate: Vec<u8> = vec![op::ret(RegId::ONE)].into_iter().collect();
3527 let owner = Input::predicate_owner(&predicate);
3528 let amount = 1000;
3529
3530 let consensus_parameters = ConsensusParameters::default();
3531 let config = Config {
3532 forbid_fake_coins_default: true,
3533 consensus_parameters: consensus_parameters.clone(),
3534 };
3535
3536 let mut tx = TransactionBuilder::script(
3537 vec![op::ret(RegId::ONE)].into_iter().collect(),
3538 vec![],
3539 )
3540 .max_fee_limit(amount)
3541 .add_input(Input::coin_predicate(
3542 rng.r#gen(),
3543 owner,
3544 amount,
3545 AssetId::BASE,
3546 rng.r#gen(),
3547 0,
3548 predicate,
3549 vec![],
3550 ))
3551 .add_output(Output::Change {
3552 to: Default::default(),
3553 amount: 0,
3554 asset_id: Default::default(),
3555 })
3556 .finalize();
3557 tx.estimate_predicates(
3558 &consensus_parameters.clone().into(),
3559 MemoryInstance::new(),
3560 &EmptyStorage,
3561 )
3562 .unwrap();
3563 let db = &mut Database::default();
3564
3565 if let Input::CoinPredicate(CoinPredicate {
3567 utxo_id,
3568 owner,
3569 amount,
3570 asset_id,
3571 tx_pointer,
3572 ..
3573 }) = tx.inputs()[0]
3574 {
3575 let mut coin = CompressedCoin::default();
3576 coin.set_owner(owner);
3577 coin.set_amount(amount);
3578 coin.set_asset_id(asset_id);
3579 coin.set_tx_pointer(tx_pointer);
3580 db.storage::<Coins>().insert(&utxo_id, &coin).unwrap();
3581 }
3582
3583 let producer = create_executor(db.clone(), config.clone());
3584
3585 let ExecutionResult {
3586 block,
3587 skipped_transactions,
3588 ..
3589 } = producer
3590 .produce_without_commit_with_source_direct_resolve(Components {
3591 header_to_produce: PartialBlockHeader::default(),
3592 transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3593 coinbase_recipient: Default::default(),
3594 gas_price: 1,
3595 })
3596 .unwrap()
3597 .into_result();
3598 assert!(skipped_transactions.is_empty());
3599
3600 let validator = create_executor(db.clone(), config);
3601 let result = validator.validate(&block);
3602 assert!(result.is_ok(), "{result:?}")
3603 }
3604
3605 #[test]
3606 fn verifying_during_production_consensus_parameters_version_works() {
3607 let mut rng = StdRng::seed_from_u64(2322u64);
3608 let predicate: Vec<u8> = vec![op::ret(RegId::ONE)].into_iter().collect();
3609 let owner = Input::predicate_owner(&predicate);
3610 let amount = 1000;
3611 let cheap_consensus_parameters = ConsensusParameters::default();
3612
3613 let mut tx = TransactionBuilder::script(vec![], vec![])
3614 .max_fee_limit(amount)
3615 .add_input(Input::coin_predicate(
3616 rng.r#gen(),
3617 owner,
3618 amount,
3619 AssetId::BASE,
3620 rng.r#gen(),
3621 0,
3622 predicate,
3623 vec![],
3624 ))
3625 .finalize();
3626 tx.estimate_predicates(
3627 &cheap_consensus_parameters.clone().into(),
3628 MemoryInstance::new(),
3629 &EmptyStorage,
3630 )
3631 .unwrap();
3632
3633 let gas_costs: GasCostsValues = GasCostsValuesV1 {
3635 vm_initialization: DependentCost::HeavyOperation {
3636 base: u32::MAX as u64,
3637 gas_per_unit: 0,
3638 },
3639 ..GasCostsValuesV1::free()
3640 }
3641 .into();
3642 let expensive_consensus_parameters_version = 0;
3643 let mut expensive_consensus_parameters = ConsensusParameters::default();
3644 expensive_consensus_parameters.set_gas_costs(gas_costs.into());
3645 expensive_consensus_parameters.set_block_gas_limit(u64::MAX);
3647 let config = Config {
3648 consensus_parameters: expensive_consensus_parameters.clone(),
3649 ..Default::default()
3650 };
3651 let producer = create_executor(Database::default(), config.clone());
3652
3653 let cheap_consensus_parameters_version = 1;
3654 let cheaply_checked_tx = MaybeCheckedTransaction::CheckedTransaction(
3655 tx.into_checked_basic(0u32.into(), &cheap_consensus_parameters)
3656 .unwrap()
3657 .into(),
3658 cheap_consensus_parameters_version,
3659 );
3660
3661 let ExecutionResult {
3663 skipped_transactions,
3664 ..
3665 } = producer
3666 .produce_without_commit_with_source_direct_resolve(Components {
3667 header_to_produce: PartialBlockHeader {
3668 application: ApplicationHeader {
3669 consensus_parameters_version:
3670 expensive_consensus_parameters_version,
3671 ..Default::default()
3672 },
3673 ..Default::default()
3674 },
3675 transactions_source: OnceTransactionsSource::new_maybe_checked(vec![
3676 cheaply_checked_tx,
3677 ]),
3678 coinbase_recipient: Default::default(),
3679 gas_price: 1,
3680 })
3681 .unwrap()
3682 .into_result();
3683
3684 assert_eq!(skipped_transactions.len(), 1);
3686 assert!(matches!(
3687 skipped_transactions[0].1,
3688 ExecutorError::InvalidTransaction(_)
3689 ));
3690 }
3691
3692 #[cfg(not(feature = "wasm-executor"))]
3693 #[tokio::test]
3694 async fn execute_block__new_transactions_trigger() {
3695 struct MockNewTransactionsTrigger {
3697 sender: tokio::sync::mpsc::Sender<()>,
3698 counter: u8,
3699 }
3700
3701 impl NewTxWaiterPort for MockNewTransactionsTrigger {
3702 async fn wait_for_new_transactions(&mut self) -> WaitNewTransactionsResult {
3703 self.sender.send(()).await.unwrap();
3704 if self.counter == 0 {
3705 self.counter += 1;
3706 WaitNewTransactionsResult::NewTransaction
3707 } else {
3708 WaitNewTransactionsResult::Timeout
3709 }
3710 }
3711 }
3712 let mut rng = StdRng::seed_from_u64(2322u64);
3713 let base_asset_id = rng.r#gen();
3714
3715 let tx = TransactionBuilder::script(vec![], vec![])
3716 .add_unsigned_coin_input(
3717 SecretKey::random(&mut rng),
3718 rng.r#gen(),
3719 1000,
3720 base_asset_id,
3721 Default::default(),
3722 )
3723 .finalize();
3724
3725 let config = Config {
3726 forbid_fake_coins_default: false,
3727 ..Default::default()
3728 };
3729 let (sender, mut receiver) = tokio::sync::mpsc::channel(2);
3730 let exec = create_executor(Database::default(), config.clone());
3731
3732 let res = exec
3734 .produce_without_commit_with_source(
3735 Components {
3736 header_to_produce: Default::default(),
3737 transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3738 gas_price: 0,
3739 coinbase_recipient: [1u8; 32].into(),
3740 },
3741 MockNewTransactionsTrigger { sender, counter: 0 },
3742 TransparentPreconfirmationSender,
3743 )
3744 .await
3745 .unwrap()
3746 .into_result();
3747
3748 receiver.recv().await.unwrap();
3750 receiver.recv().await.unwrap();
3751 assert_eq!(res.skipped_transactions.len(), 0);
3752 assert_eq!(res.block.transactions().len(), 2);
3753 }
3754
3755 #[cfg(not(feature = "wasm-executor"))]
3756 #[tokio::test]
3757 async fn execute_block__send_preconfirmations() {
3758 struct MockPreconfirmationsSender {
3760 sender: tokio::sync::mpsc::Sender<Vec<Preconfirmation>>,
3761 }
3762
3763 impl PreconfirmationSenderPort for MockPreconfirmationsSender {
3764 fn try_send(
3765 &self,
3766 preconfirmations: Vec<Preconfirmation>,
3767 ) -> Vec<Preconfirmation> {
3768 preconfirmations
3769 }
3770
3771 async fn send(&self, preconfirmations: Vec<Preconfirmation>) {
3773 self.sender.send(preconfirmations).await.unwrap();
3774 }
3775 }
3776 let mut rng = StdRng::seed_from_u64(2322u64);
3777 let base_asset_id = rng.r#gen();
3778
3779 let tx = TransactionBuilder::script(vec![], vec![])
3780 .add_unsigned_coin_input(
3781 SecretKey::random(&mut rng),
3782 rng.r#gen(),
3783 1000,
3784 base_asset_id,
3785 Default::default(),
3786 )
3787 .finalize();
3788
3789 let config = Config {
3790 forbid_fake_coins_default: false,
3791 ..Default::default()
3792 };
3793 let (sender, mut receiver) = tokio::sync::mpsc::channel(2);
3794 let exec = create_executor(Database::default(), config.clone());
3795
3796 let res = exec
3798 .produce_without_commit_with_source(
3799 Components {
3800 header_to_produce: Default::default(),
3801 transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3802 gas_price: 0,
3803 coinbase_recipient: [1u8; 32].into(),
3804 },
3805 TimeoutOnlyTxWaiter,
3806 MockPreconfirmationsSender { sender },
3807 )
3808 .await
3809 .unwrap()
3810 .into_result();
3811
3812 let preconfirmations = receiver.recv().await.unwrap();
3814 assert_eq!(preconfirmations.len(), 1);
3815 assert!(matches!(
3816 preconfirmations[0].status,
3817 PreconfirmationStatus::Success { .. }
3818 ));
3819 assert_eq!(res.skipped_transactions.len(), 0);
3820 assert_eq!(res.block.transactions().len(), 2);
3821 }
3822
3823 #[tokio::test]
3824 async fn produce_without_commit_with_source__includes_a_mint_with_whatever_gas_price_provided()
3825 {
3826 use fuel_core_executor::executor::{
3827 TimeoutOnlyTxWaiter,
3828 TransparentPreconfirmationSender,
3829 };
3830
3831 let mut rng = StdRng::seed_from_u64(2322u64);
3833 let base_asset_id = rng.r#gen();
3834 let gas_price = 1000;
3835
3836 let tx = TransactionBuilder::script(vec![], vec![])
3837 .add_unsigned_coin_input(
3838 SecretKey::random(&mut rng),
3839 rng.r#gen(),
3840 4321,
3841 base_asset_id,
3842 Default::default(),
3843 )
3844 .finalize();
3845
3846 let config = Config {
3847 forbid_fake_coins_default: false,
3848 ..Default::default()
3849 };
3850 let exec = create_executor(Database::default(), config.clone());
3851
3852 let res = exec
3854 .produce_without_commit_with_source(
3855 Components {
3856 header_to_produce: Default::default(),
3857 transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3858 gas_price,
3859 coinbase_recipient: [1u8; 32].into(),
3860 },
3861 TimeoutOnlyTxWaiter,
3862 TransparentPreconfirmationSender,
3863 )
3864 .await
3865 .unwrap()
3866 .into_result();
3867
3868 let mint = res
3870 .block
3871 .transactions()
3872 .first()
3873 .expect("all blocks should have at least one tx (the mint)")
3874 .as_mint()
3875 .expect("the last tx should be a mint");
3876 assert_eq!(mint.gas_price(), &gas_price);
3877 }
3878
3879 #[tokio::test]
3880 async fn validate__will_fail_if_gas_price_does_not_match_expected_value() {
3881 use fuel_core_executor::executor::{
3882 TimeoutOnlyTxWaiter,
3883 TransparentPreconfirmationSender,
3884 };
3885
3886 let mut rng = StdRng::seed_from_u64(2322u64);
3888 let base_asset_id = rng.r#gen();
3889 let gas_price = 1000;
3890
3891 let tx = TransactionBuilder::script(vec![], vec![])
3892 .add_unsigned_coin_input(
3893 SecretKey::random(&mut rng),
3894 rng.r#gen(),
3895 4321,
3896 base_asset_id,
3897 Default::default(),
3898 )
3899 .finalize();
3900
3901 let config = Config {
3902 forbid_fake_coins_default: false,
3903 ..Default::default()
3904 };
3905 let exec = create_executor(Database::default(), config.clone());
3906
3907 let res = exec
3909 .produce_without_commit_with_source(
3910 Components {
3911 header_to_produce: Default::default(),
3912 transactions_source: OnceTransactionsSource::new(vec![tx.into()]),
3913 gas_price,
3914 coinbase_recipient: [1u8; 32].into(),
3915 },
3916 TimeoutOnlyTxWaiter,
3917 TransparentPreconfirmationSender,
3918 )
3919 .await
3920 .unwrap()
3921 .into_result();
3922
3923 let mut block = res.block;
3925 let mint = block.transactions_mut().first_mut().unwrap();
3926 let price = mint.as_mint_mut().unwrap().gas_price_mut();
3927 *price = gas_price * 2;
3928
3929 let res = exec.validate(&block);
3930 assert!(
3931 matches!(res, Err(ExecutorError::BlockMismatch)),
3932 "Expected BlockMismatch error, got: {res:?}"
3933 );
3934 }
3935
3936 #[test]
3937 #[cfg(not(feature = "wasm-executor"))]
3938 fn block_producer_never_includes_more_than_max_tx_count_transactions() {
3939 use fuel_core_executor::executor::max_tx_count;
3940
3941 let block_height = 1u32;
3942 let block_da_height = 2u64;
3943
3944 let mut consensus_parameters = ConsensusParameters::default();
3945
3946 let transactions_in_tx_source = (max_tx_count() as usize) + 10;
3948 consensus_parameters.set_block_gas_limit(u64::MAX);
3949 let config = Config {
3950 consensus_parameters,
3951 ..Default::default()
3952 };
3953
3954 let block = test_block(
3956 block_height.into(),
3957 block_da_height.into(),
3958 transactions_in_tx_source,
3959 );
3960 let partial_fuel_block: PartialFuelBlock = block.into();
3961
3962 let producer = create_executor(Database::default(), config);
3963 let (result, _) = producer
3964 .produce_without_commit(partial_fuel_block)
3965 .unwrap()
3966 .into();
3967
3968 assert_eq!(
3970 result.block.transactions().len(),
3971 (max_tx_count() as usize + 1)
3972 );
3973 }
3974
3975 #[test]
3976 #[cfg(not(feature = "wasm-executor"))]
3977 fn block_producer_never_includes_more_than_max_tx_count_transactions_with_bad_tx_source()
3978 {
3979 use fuel_core_executor::executor::max_tx_count;
3980 use std::sync::Mutex;
3981
3982 pub struct BadTransactionsSource {
3986 transactions: Mutex<Vec<MaybeCheckedTransaction>>,
3987 }
3988
3989 impl BadTransactionsSource {
3990 pub fn new(transactions: Vec<Transaction>) -> Self {
3991 Self {
3992 transactions: Mutex::new(
3993 transactions
3994 .into_iter()
3995 .map(MaybeCheckedTransaction::Transaction)
3996 .collect(),
3997 ),
3998 }
3999 }
4000 }
4001
4002 impl fuel_core_executor::ports::TransactionsSource for BadTransactionsSource {
4003 fn next(&self, _: u64, _: u16, _: u32) -> Vec<MaybeCheckedTransaction> {
4004 std::mem::take(&mut *self.transactions.lock().unwrap())
4005 }
4006 }
4007
4008 let block_height = 1u32;
4009 let block_da_height = 2u64;
4010
4011 let mut consensus_parameters = ConsensusParameters::default();
4012
4013 let transactions_in_tx_source = (max_tx_count() as usize) + 10;
4015 consensus_parameters.set_block_gas_limit(u64::MAX);
4016 let config = Config {
4017 consensus_parameters,
4018 ..Default::default()
4019 };
4020
4021 let block = test_block(
4022 block_height.into(),
4023 block_da_height.into(),
4024 transactions_in_tx_source,
4025 );
4026 let partial_fuel_block: PartialFuelBlock = block.into();
4027 let components = Components {
4028 header_to_produce: partial_fuel_block.header,
4029 transactions_source: BadTransactionsSource::new(
4030 partial_fuel_block.transactions,
4031 ),
4032 coinbase_recipient: Default::default(),
4033 gas_price: 0,
4034 };
4035
4036 let producer = create_executor(Database::default(), config);
4038 let (result, _) = producer
4039 .produce_without_commit_with_source_direct_resolve(components)
4040 .unwrap()
4041 .into();
4042
4043 assert_eq!(
4045 result.block.transactions().len(),
4046 (max_tx_count() as usize + 1)
4047 );
4048 }
4049
4050 #[cfg(feature = "relayer")]
4051 mod relayer {
4052 use super::*;
4053 use crate::database::database_description::{
4054 on_chain::OnChain,
4055 relayer::Relayer,
4056 };
4057 use fuel_core_relayer::storage::EventsHistory;
4058 use fuel_core_storage::{
4059 StorageAsMut,
4060 column::Column,
4061 iter::{
4062 IteratorOverTable,
4063 changes_iterator::ChangesIterator,
4064 },
4065 tables::FuelBlocks,
4066 transactional::StorageChanges,
4067 };
4068 use fuel_core_types::{
4069 entities::RelayedTransaction,
4070 fuel_merkle::binary::root_calculator::MerkleRootCalculator,
4071 fuel_tx::{
4072 Chargeable,
4073 output,
4074 },
4075 services::executor::ForcedTransactionFailure,
4076 };
4077
4078 fn database_with_genesis_block(da_block_height: u64) -> Database<OnChain> {
4079 let mut db = add_consensus_parameters(
4080 Database::default(),
4081 &ConsensusParameters::default(),
4082 );
4083 let mut block = Block::default();
4084 block.header_mut().set_da_height(da_block_height.into());
4085 block.header_mut().recalculate_metadata();
4086
4087 db.storage_as_mut::<FuelBlocks>()
4088 .insert(&0.into(), &block)
4089 .expect("Should insert genesis block without any problems");
4090 db
4091 }
4092
4093 fn add_message_to_relayer(db: &mut Database<Relayer>, message: Message) {
4094 let da_height = message.da_height();
4095 db.storage::<EventsHistory>()
4096 .insert(&da_height, &[Event::Message(message)])
4097 .expect("Should insert event");
4098 }
4099
4100 fn add_events_to_relayer(
4101 db: &mut Database<Relayer>,
4102 da_height: DaBlockHeight,
4103 events: &[Event],
4104 ) {
4105 db.storage::<EventsHistory>()
4106 .insert(&da_height, events)
4107 .expect("Should insert event");
4108 }
4109
4110 fn add_messages_to_relayer(db: &mut Database<Relayer>, relayer_da_height: u64) {
4111 for da_height in 0..=relayer_da_height {
4112 let mut message = Message::default();
4113 message.set_da_height(da_height.into());
4114 message.set_nonce(da_height.into());
4115
4116 add_message_to_relayer(db, message);
4117 }
4118 }
4119
4120 fn create_relayer_executor(
4121 on_chain: Database<OnChain>,
4122 relayer: Database<Relayer>,
4123 ) -> Executor<Database<OnChain>, Database<Relayer>> {
4124 Executor::new(on_chain, relayer, Default::default())
4125 }
4126
4127 struct Input {
4128 relayer_da_height: u64,
4129 block_height: u32,
4130 block_da_height: u64,
4131 genesis_da_height: Option<u64>,
4132 }
4133
4134 #[test_case::test_case(
4135 Input {
4136 relayer_da_height: 10,
4137 block_height: 1,
4138 block_da_height: 10,
4139 genesis_da_height: Some(0),
4140 } => matches Ok(()); "block producer takes all 10 messages from the relayer"
4141 )]
4142 #[test_case::test_case(
4143 Input {
4144 relayer_da_height: 10,
4145 block_height: 1,
4146 block_da_height: 5,
4147 genesis_da_height: Some(0),
4148 } => matches Ok(()); "block producer takes first 5 messages from the relayer"
4149 )]
4150 #[test_case::test_case(
4151 Input {
4152 relayer_da_height: 10,
4153 block_height: 1,
4154 block_da_height: 10,
4155 genesis_da_height: Some(5),
4156 } => matches Ok(()); "block producer takes last 5 messages from the relayer"
4157 )]
4158 #[test_case::test_case(
4159 Input {
4160 relayer_da_height: 10,
4161 block_height: 1,
4162 block_da_height: 10,
4163 genesis_da_height: Some(u64::MAX),
4164 } => matches Err(ExecutorError::DaHeightExceededItsLimit); "block producer fails when previous block exceeds `u64::MAX`"
4165 )]
4166 #[test_case::test_case(
4167 Input {
4168 relayer_da_height: 10,
4169 block_height: 1,
4170 block_da_height: 10,
4171 genesis_da_height: None,
4172 } => matches Err(ExecutorError::PreviousBlockIsNotFound); "block producer fails when previous block doesn't exist"
4173 )]
4174 #[test_case::test_case(
4175 Input {
4176 relayer_da_height: 10,
4177 block_height: 0,
4178 block_da_height: 10,
4179 genesis_da_height: Some(0),
4180 } => matches Err(ExecutorError::ExecutingGenesisBlock); "block producer fails when block height is zero"
4181 )]
4182 fn block_producer_takes_messages_from_the_relayer(
4183 input: Input,
4184 ) -> Result<(), ExecutorError> {
4185 let genesis_da_height = input.genesis_da_height.unwrap_or_default();
4186 let on_chain_db = if let Some(genesis_da_height) = input.genesis_da_height {
4187 database_with_genesis_block(genesis_da_height)
4188 } else {
4189 add_consensus_parameters(
4190 Database::default(),
4191 &ConsensusParameters::default(),
4192 )
4193 };
4194 let mut relayer_db = Database::<Relayer>::default();
4195
4196 let relayer_da_height = input.relayer_da_height;
4198 let block_height = input.block_height;
4199 let block_da_height = input.block_da_height;
4200 add_messages_to_relayer(&mut relayer_db, relayer_da_height);
4201 assert_eq!(on_chain_db.iter_all::<Messages>(None).count(), 0);
4202
4203 let producer = create_relayer_executor(on_chain_db, relayer_db);
4205 let block = test_block(block_height.into(), block_da_height.into(), 0);
4206 let (result, changes) = producer.produce_without_commit(block.into())?.into();
4207
4208 let changes = StorageChanges::Changes(changes);
4210 let view = ChangesIterator::<Column>::new(&changes);
4211 assert_eq!(
4212 view.iter_all::<Messages>(None).count() as u64,
4213 block_da_height - genesis_da_height
4214 );
4215 assert_eq!(
4216 result.events.len() as u64,
4217 block_da_height - genesis_da_height
4218 );
4219 let messages = view.iter_all::<Messages>(None);
4220 for ((da_height, message), event) in (genesis_da_height + 1..block_da_height)
4221 .zip(messages)
4222 .zip(result.events.iter())
4223 {
4224 let (_, message) = message.unwrap();
4225 assert_eq!(message.da_height(), da_height.into());
4226 assert!(matches!(event, ExecutorEvent::MessageImported(_)));
4227 }
4228 Ok(())
4229 }
4230
4231 #[test]
4232 fn execute_without_commit__block_producer_includes_correct_inbox_event_merkle_root()
4233 {
4234 let genesis_da_height = 3u64;
4236 let on_chain_db = database_with_genesis_block(genesis_da_height);
4237 let mut relayer_db = Database::<Relayer>::default();
4238 let block_height = 1u32;
4239 let relayer_da_height = 10u64;
4240 let mut root_calculator = MerkleRootCalculator::new();
4241 for da_height in (genesis_da_height + 1)..=relayer_da_height {
4242 let mut message = Message::default();
4244 message.set_da_height(da_height.into());
4245 message.set_nonce(da_height.into());
4246 root_calculator.push(message.message_id().as_ref());
4247 let mut transaction = RelayedTransaction::default();
4249 transaction.set_nonce(da_height.into());
4250 transaction.set_da_height(da_height.into());
4251 transaction.set_max_gas(da_height);
4252 transaction.set_serialized_transaction(da_height.to_be_bytes().to_vec());
4253 root_calculator.push(Bytes32::from(transaction.id()).as_ref());
4254 add_events_to_relayer(
4256 &mut relayer_db,
4257 da_height.into(),
4258 &[message.into(), transaction.into()],
4259 );
4260 }
4261 let producer = create_relayer_executor(on_chain_db, relayer_db);
4262 let block = test_block(block_height.into(), relayer_da_height.into(), 0);
4263
4264 let (result, _) = producer
4266 .produce_without_commit(block.into())
4267 .unwrap()
4268 .into();
4269
4270 let expected = root_calculator.root().into();
4272 let actual = result.block.header().event_inbox_root();
4273 assert_eq!(actual, expected);
4274 }
4275
4276 #[test]
4277 fn execute_without_commit__relayed_tx_included_in_block() {
4278 let genesis_da_height = 3u64;
4279 let block_height = 1u32;
4280 let da_height = 10u64;
4281 let arb_large_max_gas = 10_000;
4282
4283 let relayer_db =
4285 relayer_db_with_valid_relayed_txs(da_height, arb_large_max_gas);
4286
4287 let on_chain_db = database_with_genesis_block(genesis_da_height);
4289 let producer = create_relayer_executor(on_chain_db, relayer_db);
4290 let block = test_block(block_height.into(), da_height.into(), 0);
4291 let (result, _) = producer
4292 .produce_without_commit(block.into())
4293 .unwrap()
4294 .into();
4295
4296 let txs = result.block.transactions();
4298 assert_eq!(txs.len(), 2);
4299 }
4300
4301 fn relayer_db_with_valid_relayed_txs(
4302 da_height: u64,
4303 max_gas: u64,
4304 ) -> Database<Relayer> {
4305 let mut relayed_tx = RelayedTransaction::default();
4306 let tx = script_tx_for_amount(100);
4307 let tx_bytes = tx.to_bytes();
4308 relayed_tx.set_serialized_transaction(tx_bytes);
4309 relayed_tx.set_max_gas(max_gas);
4310
4311 relayer_db_for_events(&[relayed_tx.into()], da_height)
4312 }
4313
4314 #[test]
4315 fn execute_without_commit_with_coinbase__relayed_tx_execute_and_mint_will_have_no_fees()
4316 {
4317 let genesis_da_height = 3u64;
4318 let block_height = 1u32;
4319 let da_height = 10u64;
4320 let gas_price = 1;
4321 let arb_max_gas = 10_000;
4322
4323 let relayer_db = relayer_db_with_valid_relayed_txs(da_height, arb_max_gas);
4325
4326 let on_chain_db = database_with_genesis_block(genesis_da_height);
4328 let producer = create_relayer_executor(on_chain_db, relayer_db);
4329 let block = test_block(block_height.into(), da_height.into(), 0);
4330 let (result, _) = producer
4331 .produce_without_commit_with_coinbase(
4332 block.into(),
4333 Default::default(),
4334 gas_price,
4335 )
4336 .unwrap()
4337 .into();
4338
4339 let txs = result.block.transactions();
4341 assert_eq!(txs.len(), 2);
4342
4343 let mint = txs[1].as_mint().unwrap();
4345 assert_eq!(*mint.mint_amount(), 0);
4346 }
4347
4348 #[test]
4349 fn execute_without_commit__duplicated_relayed_tx_not_included_in_block() {
4350 let genesis_da_height = 3u64;
4351 let block_height = 1u32;
4352 let da_height = 10u64;
4353 let duplicate_count = 10;
4354 let arb_large_max_gas = 10_000;
4355
4356 let relayer_db = relayer_db_with_duplicate_valid_relayed_txs(
4358 da_height,
4359 duplicate_count,
4360 arb_large_max_gas,
4361 );
4362
4363 let on_chain_db = database_with_genesis_block(genesis_da_height);
4365 let producer = create_relayer_executor(on_chain_db, relayer_db);
4366 let block = test_block(block_height.into(), da_height.into(), 0);
4367 let (result, _) = producer
4368 .produce_without_commit(block.into())
4369 .unwrap()
4370 .into();
4371
4372 let txs = result.block.transactions();
4374 assert_eq!(txs.len(), 2);
4375
4376 let events = result.events;
4378 let count = events
4379 .into_iter()
4380 .filter(|event| {
4381 matches!(event, ExecutorEvent::ForcedTransactionFailed { .. })
4382 })
4383 .count();
4384 assert_eq!(count, 10);
4385 }
4386
4387 fn relayer_db_with_duplicate_valid_relayed_txs(
4388 da_height: u64,
4389 duplicate_count: usize,
4390 max_gas: u64,
4391 ) -> Database<Relayer> {
4392 let mut relayed_tx = RelayedTransaction::default();
4393 let tx = script_tx_for_amount(100);
4394 let tx_bytes = tx.to_bytes();
4395 relayed_tx.set_serialized_transaction(tx_bytes);
4396 relayed_tx.set_max_gas(max_gas);
4397 let events = std::iter::repeat_n(relayed_tx.into(), duplicate_count + 1)
4398 .collect::<Vec<_>>();
4399
4400 relayer_db_for_events(&events, da_height)
4401 }
4402
4403 #[test]
4404 fn execute_without_commit__invalid_relayed_txs_are_not_included_and_are_reported()
4405 {
4406 let genesis_da_height = 3u64;
4407 let block_height = 1u32;
4408 let da_height = 10u64;
4409 let arb_large_max_gas = 10_000;
4410
4411 let relayer_db =
4413 relayer_db_with_invalid_relayed_txs(da_height, arb_large_max_gas);
4414
4415 let on_chain_db = database_with_genesis_block(genesis_da_height);
4417 let producer = create_relayer_executor(on_chain_db, relayer_db);
4418 let block = test_block(block_height.into(), da_height.into(), 0);
4419 let (result, _) = producer
4420 .produce_without_commit(block.into())
4421 .unwrap()
4422 .into();
4423
4424 let txs = result.block.transactions();
4426 assert_eq!(txs.len(), 1);
4427
4428 let events = result.events;
4430 let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4431 failure: actual,
4432 ..
4433 } = &events[0]
4434 else {
4435 panic!("Expected `ForcedTransactionFailed` event")
4436 };
4437 let expected = &ForcedTransactionFailure::CheckError(CheckError::Validity(
4438 ValidityError::NoSpendableInput,
4439 ))
4440 .to_string();
4441 assert_eq!(expected, actual);
4442 }
4443
4444 fn relayer_db_with_invalid_relayed_txs(
4445 da_height: u64,
4446 max_gas: u64,
4447 ) -> Database<Relayer> {
4448 let event = arb_invalid_relayed_tx_event(max_gas);
4449 relayer_db_for_events(&[event], da_height)
4450 }
4451
4452 #[test]
4453 fn execute_without_commit__relayed_tx_with_low_max_gas_fails() {
4454 let genesis_da_height = 3u64;
4455 let block_height = 1u32;
4456 let da_height = 10u64;
4457 let zero_max_gas = 0;
4458
4459 let tx = script_tx_for_amount(100);
4461
4462 let relayer_db = relayer_db_with_specific_tx_for_relayed_tx(
4463 da_height,
4464 tx.clone(),
4465 zero_max_gas,
4466 );
4467
4468 let on_chain_db = database_with_genesis_block(genesis_da_height);
4470 let producer = create_relayer_executor(on_chain_db, relayer_db);
4471 let block = test_block(block_height.into(), da_height.into(), 0);
4472 let (result, _) = producer
4473 .produce_without_commit(block.into())
4474 .unwrap()
4475 .into();
4476
4477 let txs = result.block.transactions();
4479 assert_eq!(txs.len(), 1);
4480
4481 let consensus_params = ConsensusParameters::default();
4483 let actual_max_gas = tx
4484 .as_script()
4485 .unwrap()
4486 .max_gas(consensus_params.gas_costs(), consensus_params.fee_params());
4487 let events = result.events;
4488 let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4489 failure: actual,
4490 ..
4491 } = &events[0]
4492 else {
4493 panic!("Expected `ForcedTransactionFailed` event")
4494 };
4495 let expected = &ForcedTransactionFailure::InsufficientMaxGas {
4496 claimed_max_gas: zero_max_gas,
4497 actual_max_gas,
4498 }
4499 .to_string();
4500 assert_eq!(expected, actual);
4501 }
4502
4503 fn relayer_db_with_specific_tx_for_relayed_tx(
4504 da_height: u64,
4505 tx: Transaction,
4506 max_gas: u64,
4507 ) -> Database<Relayer> {
4508 let mut relayed_tx = RelayedTransaction::default();
4509 let tx_bytes = tx.to_bytes();
4510 relayed_tx.set_serialized_transaction(tx_bytes);
4511 relayed_tx.set_max_gas(max_gas);
4512 relayer_db_for_events(&[relayed_tx.into()], da_height)
4513 }
4514
4515 #[test]
4516 fn execute_without_commit__relayed_tx_that_passes_checks_but_fails_execution_is_reported()
4517 {
4518 let genesis_da_height = 3u64;
4519 let block_height = 1u32;
4520 let da_height = 10u64;
4521 let arb_max_gas = 10_000;
4522
4523 let (tx_id, relayer_db) =
4525 tx_id_and_relayer_db_with_tx_that_passes_checks_but_fails_execution(
4526 da_height,
4527 arb_max_gas,
4528 );
4529
4530 let on_chain_db = database_with_genesis_block(genesis_da_height);
4532 let producer = create_relayer_executor(on_chain_db, relayer_db);
4533 let block = test_block(block_height.into(), da_height.into(), 0);
4534 let (result, _) = producer
4535 .produce_without_commit(block.into())
4536 .unwrap()
4537 .into();
4538
4539 let txs = result.block.transactions();
4541 assert_eq!(txs.len(), 2);
4542
4543 let events = result.events;
4545 let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4546 failure: actual,
4547 ..
4548 } = &events[3]
4549 else {
4550 panic!("Expected `ForcedTransactionFailed` event")
4551 };
4552 let expected =
4553 &fuel_core_types::services::executor::Error::TransactionIdCollision(
4554 tx_id,
4555 )
4556 .to_string();
4557 assert_eq!(expected, actual);
4558 }
4559
4560 fn tx_id_and_relayer_db_with_tx_that_passes_checks_but_fails_execution(
4561 da_height: u64,
4562 max_gas: u64,
4563 ) -> (Bytes32, Database<Relayer>) {
4564 let mut relayed_tx = RelayedTransaction::default();
4565 let tx = script_tx_for_amount(100);
4566 let tx_bytes = tx.to_bytes();
4567 relayed_tx.set_serialized_transaction(tx_bytes);
4568 relayed_tx.set_max_gas(max_gas);
4569 let mut bad_relayed_tx = relayed_tx.clone();
4570 let new_nonce = [9; 32].into();
4571 bad_relayed_tx.set_nonce(new_nonce);
4572 let relayer_db = relayer_db_for_events(
4573 &[relayed_tx.into(), bad_relayed_tx.into()],
4574 da_height,
4575 );
4576 (tx.id(&Default::default()), relayer_db)
4577 }
4578
4579 #[test]
4580 fn execute_without_commit__validation__includes_status_of_failed_relayed_tx() {
4581 let genesis_da_height = 3u64;
4582 let block_height = 1u32;
4583 let da_height = 10u64;
4584 let arb_large_max_gas = 10_000;
4585
4586 let event = arb_invalid_relayed_tx_event(arb_large_max_gas);
4588 let produced_block = produce_block_with_relayed_event(
4589 event.clone(),
4590 genesis_da_height,
4591 block_height,
4592 da_height,
4593 );
4594
4595 let verifier_db = database_with_genesis_block(genesis_da_height);
4597 let mut verifier_relayer_db = Database::<Relayer>::default();
4598 let events = vec![event];
4599 add_events_to_relayer(&mut verifier_relayer_db, da_height.into(), &events);
4600 let verifier = create_relayer_executor(verifier_db, verifier_relayer_db);
4601 let (result, _) = verifier.validate(&produced_block).unwrap().into();
4602
4603 let txs = produced_block.transactions();
4605 assert_eq!(txs.len(), 1);
4606
4607 let events = result.events;
4609 let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4610 failure: actual,
4611 ..
4612 } = &events[0]
4613 else {
4614 panic!("Expected `ForcedTransactionFailed` event")
4615 };
4616 let expected = &ForcedTransactionFailure::CheckError(CheckError::Validity(
4617 ValidityError::NoSpendableInput,
4618 ))
4619 .to_string();
4620 assert_eq!(expected, actual);
4621 }
4622
4623 fn produce_block_with_relayed_event(
4624 event: Event,
4625 genesis_da_height: u64,
4626 block_height: u32,
4627 da_height: u64,
4628 ) -> Block {
4629 let producer_db = database_with_genesis_block(genesis_da_height);
4630 let producer_relayer_db = relayer_db_for_events(&[event], da_height);
4631
4632 let producer = create_relayer_executor(producer_db, producer_relayer_db);
4633 let block = test_block(block_height.into(), da_height.into(), 0);
4634 let (produced_result, _) = producer
4635 .produce_without_commit(block.into())
4636 .unwrap()
4637 .into();
4638 produced_result.block
4639 }
4640
4641 fn arb_invalid_relayed_tx_event(max_gas: u64) -> Event {
4642 let mut invalid_relayed_tx = RelayedTransaction::default();
4643 let mut tx = script_tx_for_amount(100);
4644 tx.as_script_mut().unwrap().inputs_mut().drain(..); let tx_bytes = tx.to_bytes();
4646 invalid_relayed_tx.set_serialized_transaction(tx_bytes);
4647 invalid_relayed_tx.set_max_gas(max_gas);
4648 invalid_relayed_tx.into()
4649 }
4650
4651 #[test]
4652 fn execute_without_commit__relayed_mint_tx_not_included_in_block() {
4653 let genesis_da_height = 3u64;
4654 let block_height = 1u32;
4655 let da_height = 10u64;
4656 let tx_count = 0;
4657
4658 let relayer_db =
4660 relayer_db_with_mint_relayed_tx(da_height, block_height, tx_count);
4661
4662 let on_chain_db = database_with_genesis_block(genesis_da_height);
4664 let producer = create_relayer_executor(on_chain_db, relayer_db);
4665 let block =
4666 test_block(block_height.into(), da_height.into(), tx_count as usize);
4667 let (result, _) = producer
4668 .produce_without_commit(block.into())
4669 .unwrap()
4670 .into();
4671
4672 let txs = result.block.transactions();
4674 assert_eq!(txs.len(), 1);
4675
4676 let events = result.events;
4678 let fuel_core_types::services::executor::Event::ForcedTransactionFailed {
4679 failure: actual,
4680 ..
4681 } = &events[0]
4682 else {
4683 panic!("Expected `ForcedTransactionFailed` event")
4684 };
4685 let expected = &ForcedTransactionFailure::InvalidTransactionType.to_string();
4686 assert_eq!(expected, actual);
4687 }
4688
4689 fn relayer_db_with_mint_relayed_tx(
4690 da_height: u64,
4691 block_height: u32,
4692 tx_count: u16,
4693 ) -> Database<Relayer> {
4694 let mut relayed_tx = RelayedTransaction::default();
4695 let base_asset_id = AssetId::BASE;
4696 let mint = Transaction::mint(
4697 TxPointer::new(block_height.into(), tx_count),
4698 contract::Contract {
4699 utxo_id: UtxoId::new(Bytes32::zeroed(), 0),
4700 balance_root: Bytes32::zeroed(),
4701 state_root: Bytes32::zeroed(),
4702 tx_pointer: TxPointer::new(BlockHeight::new(0), 0),
4703 contract_id: ContractId::zeroed(),
4704 },
4705 output::contract::Contract {
4706 input_index: 0,
4707 balance_root: Bytes32::zeroed(),
4708 state_root: Bytes32::zeroed(),
4709 },
4710 0,
4711 base_asset_id,
4712 0,
4713 );
4714 let tx = Transaction::Mint(mint);
4715 let tx_bytes = tx.to_bytes();
4716 relayed_tx.set_serialized_transaction(tx_bytes);
4717 relayer_db_for_events(&[relayed_tx.into()], da_height)
4718 }
4719
4720 fn relayer_db_for_events(events: &[Event], da_height: u64) -> Database<Relayer> {
4721 let mut relayer_db = Database::<Relayer>::default();
4722 add_events_to_relayer(&mut relayer_db, da_height.into(), events);
4723 relayer_db
4724 }
4725
4726 #[test]
4727 fn execute_without_commit__relayed_tx_can_spend_message_from_same_da_block() {
4728 let genesis_da_height = 3u64;
4729 let block_height = 1u32;
4730 let da_height = 10u64;
4731 let arb_max_gas = 10_000;
4732
4733 let relayer_db =
4735 relayer_db_with_relayed_tx_spending_message_from_same_da_block(
4736 da_height,
4737 arb_max_gas,
4738 );
4739
4740 let on_chain_db = database_with_genesis_block(genesis_da_height);
4742 let producer =
4743 create_relayer_executor(on_chain_db.clone(), relayer_db.clone());
4744 let block = test_block(block_height.into(), da_height.into(), 0);
4745 let (result, _) = producer
4746 .produce_without_commit(block.into())
4747 .unwrap()
4748 .into();
4749
4750 let txs = result.block.transactions();
4752 assert_eq!(txs.len(), 2);
4753
4754 let validator = create_relayer_executor(on_chain_db, relayer_db);
4755 let result = validator.validate(&result.block).map(|_| ());
4757
4758 assert_eq!(Ok(()), result);
4760 }
4761
4762 fn relayer_db_with_relayed_tx_spending_message_from_same_da_block(
4763 da_height: u64,
4764 max_gas: u64,
4765 ) -> Database<Relayer> {
4766 let mut relayer_db = Database::<Relayer>::default();
4767 let mut message = Message::default();
4768 let nonce = 1.into();
4769 message.set_da_height(da_height.into());
4770 message.set_nonce(nonce);
4771 let message_event = Event::Message(message);
4772
4773 let mut relayed_tx = RelayedTransaction::default();
4774 let tx = TransactionBuilder::script(vec![], vec![])
4775 .script_gas_limit(10)
4776 .add_unsigned_message_input(
4777 SecretKey::random(&mut StdRng::seed_from_u64(2322)),
4778 Default::default(),
4779 nonce,
4780 Default::default(),
4781 vec![],
4782 )
4783 .finalize_as_transaction();
4784 let tx_bytes = tx.to_bytes();
4785 relayed_tx.set_serialized_transaction(tx_bytes);
4786 relayed_tx.set_max_gas(max_gas);
4787 let tx_event = Event::Transaction(relayed_tx);
4788 add_events_to_relayer(
4789 &mut relayer_db,
4790 da_height.into(),
4791 &[message_event, tx_event],
4792 );
4793 relayer_db
4794 }
4795
4796 #[test]
4797 fn block_producer_does_not_take_messages_for_the_same_height() {
4798 let genesis_da_height = 1u64;
4799 let on_chain_db = database_with_genesis_block(genesis_da_height);
4800 let mut relayer_db = Database::<Relayer>::default();
4801
4802 let relayer_da_height = 10u64;
4804 let block_height = 1u32;
4805 let block_da_height = 1u64;
4806 add_messages_to_relayer(&mut relayer_db, relayer_da_height);
4807 assert_eq!(on_chain_db.iter_all::<Messages>(None).count(), 0);
4808
4809 let producer = create_relayer_executor(on_chain_db, relayer_db);
4811 let block = test_block(block_height.into(), block_da_height.into(), 10);
4812 let (result, changes) = producer
4813 .produce_without_commit(block.into())
4814 .unwrap()
4815 .into();
4816
4817 let changes = StorageChanges::Changes(changes);
4819 let view = ChangesIterator::<Column>::new(&changes);
4820 assert!(result.skipped_transactions.is_empty());
4821 assert_eq!(view.iter_all::<Messages>(None).count() as u64, 0);
4822 }
4823
4824 #[test]
4825 fn block_producer_can_use_just_added_message_in_the_transaction() {
4826 let genesis_da_height = 1u64;
4827 let on_chain_db = database_with_genesis_block(genesis_da_height);
4828 let mut relayer_db = Database::<Relayer>::default();
4829
4830 let block_height = 1u32;
4831 let block_da_height = 2u64;
4832 let nonce = 1.into();
4833 let mut message = Message::default();
4834 message.set_da_height(block_da_height.into());
4835 message.set_nonce(nonce);
4836 add_message_to_relayer(&mut relayer_db, message);
4837
4838 assert_eq!(on_chain_db.iter_all::<Messages>(None).count(), 0);
4840 let tx = TransactionBuilder::script(vec![], vec![])
4841 .script_gas_limit(10)
4842 .add_unsigned_message_input(
4843 SecretKey::random(&mut StdRng::seed_from_u64(2322)),
4844 Default::default(),
4845 nonce,
4846 Default::default(),
4847 vec![],
4848 )
4849 .finalize_as_transaction();
4850
4851 let mut block = test_block(block_height.into(), block_da_height.into(), 0);
4853 *block.transactions_mut() = vec![tx];
4854 let producer = create_relayer_executor(on_chain_db, relayer_db);
4855 let (result, changes) = producer
4856 .produce_without_commit(block.into())
4857 .unwrap()
4858 .into();
4859
4860 let changes = StorageChanges::Changes(changes);
4862 let view = ChangesIterator::<Column>::new(&changes);
4863 assert!(result.skipped_transactions.is_empty());
4864 assert_eq!(view.iter_all::<Messages>(None).count() as u64, 0);
4865 assert_eq!(result.events.len(), 2);
4866 assert!(matches!(
4867 result.events[0],
4868 ExecutorEvent::MessageImported(_)
4869 ));
4870 assert!(matches!(
4871 result.events[1],
4872 ExecutorEvent::MessageConsumed(_)
4873 ));
4874 }
4875 }
4876}