griffin_core/
executive.rs

1//! # Executive Module
2//!
3//! The executive is the main orchestrator for the entire runtime.
4//! It has functions that implement the Core, BlockBuilder, and TxPool runtime APIs.
5//!
6//! It does all the reusable verification of UTXO transactions.
7
8use crate::pallas_applying::{
9    babbage::{
10        check_ins_not_empty,
11        // check_all_ins_in_utxos,
12        check_preservation_of_value,
13        check_tx_validity_interval,
14        check_witness_set,
15    },
16    utils::BabbageError::*,
17    UTxOs,
18};
19use crate::pallas_codec::utils::CborWrap;
20use crate::pallas_primitives::{
21    babbage::{
22        MintedDatumOption, MintedScriptRef, MintedTransactionBody, MintedTx,
23        Tx as PallasTransaction, Value as PallasValue,
24    },
25    conway::{MintedTx as ConwayMintedTx, TransactionOutput},
26};
27use crate::uplc::tx::{eval_phase_two, ResolvedInput, SlotConfig};
28use crate::{
29    checks_interface::{
30        babbage_minted_tx_from_cbor, babbage_tx_to_cbor, check_min_coin,
31        conway_minted_tx_from_cbor, mk_utxo_for_babbage_tx,
32    },
33    ensure,
34    types::{Block, BlockNumber, DispatchResult, Header, Input, Output, Transaction, UTxOError},
35    utxo_set::TransparentUtxoSet,
36    EXTRINSIC_KEY, HEADER_KEY, HEIGHT_KEY, LOG_TARGET,
37};
38use crate::{MILLI_SECS_PER_SLOT, ZERO_SLOT, ZERO_TIME};
39use alloc::{collections::btree_set::BTreeSet, string::String, vec::Vec};
40use log::debug;
41use parity_scale_codec::{Decode, Encode};
42use sp_runtime::{
43    traits::{BlakeTwo256, Block as BlockT, Extrinsic, Hash as HashT, Header as HeaderT},
44    transaction_validity::{
45        TransactionLongevity, TransactionSource, TransactionValidity, TransactionValidityError,
46        ValidTransaction,
47    },
48    ApplyExtrinsicResult, ExtrinsicInclusionMode, StateVersion,
49};
50
51type OutputInfoList<'a> = Vec<(
52    String, // address in string format
53    PallasValue,
54    Option<MintedDatumOption<'a>>,
55    Option<CborWrap<MintedScriptRef<'a>>>,
56)>;
57
58/// The executive is in charge of validating transactions for admittance in the
59/// pool and in blocks. It is in charge of *executing* transactions, i.e.,
60/// applying them to the ledger.
61pub struct Executive;
62
63impl Executive
64where
65    Block: BlockT,
66    Transaction: Extrinsic,
67{
68    /// Checks performed to enter the transaction pool. The response of the node
69    /// is essentially determined by the outcome of this function.
70    fn pool_checks(mtx: &MintedTx, _utxos: &UTxOs) -> DispatchResult {
71        check_ins_not_empty(&mtx.transaction_body.clone())?;
72        Ok(())
73    }
74
75    /// Checks performed to a transaction with all its requirements satisfied
76    /// to be included in a block.
77    fn ledger_checks(mtx: &MintedTx, utxos: &UTxOs) -> DispatchResult {
78        let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone();
79        // Next unneeded since already checked at `apply_griffin_transaction`
80        // check_all_ins_in_utxos(tx_body, utxos)?;
81        let current_slot = Self::zero_slot() + (Self::block_height() as u64);
82        check_tx_validity_interval(tx_body, &current_slot)?;
83        check_preservation_of_value(tx_body, utxos)?;
84        check_witness_set(mtx, utxos)?;
85        check_min_coin(tx_body)?;
86
87        Ok(())
88    }
89
90    fn phase_two_checks(tx_cbor_bytes: &Vec<u8>, input_utxos: Vec<Output>) -> DispatchResult {
91        let conway_mtx: ConwayMintedTx = conway_minted_tx_from_cbor(&tx_cbor_bytes);
92        let pallas_input_utxos = input_utxos
93            .iter()
94            .map(|ri| TransactionOutput::from(ri.clone()))
95            .collect::<Vec<_>>();
96        let pallas_resolved_inputs: Vec<ResolvedInput> = conway_mtx
97            .transaction_body
98            .inputs
99            .iter()
100            .zip(pallas_input_utxos.iter())
101            .map(|(input, output)| ResolvedInput {
102                input: input.clone(),
103                output: output.clone(),
104            })
105            .collect();
106
107        let slot_config = SlotConfig {
108            zero_time: Self::zero_time(),
109            zero_slot: Self::zero_slot(),
110            slot_length: MILLI_SECS_PER_SLOT,
111        };
112        let phase_two_result = eval_phase_two(
113            &conway_mtx,
114            &pallas_resolved_inputs,
115            None,
116            None,
117            &slot_config,
118            false,
119            |_| (),
120        );
121        ensure!(
122            phase_two_result.is_ok(),
123            UTxOError::PhaseTwo(phase_two_result.unwrap_err())
124        );
125
126        Ok(())
127    }
128
129    /// Does pool-style validation of a griffin transaction.
130    /// Does not commit anything to storage.
131    /// This returns Ok even if some inputs are still missing because the tagged transaction pool can handle that.
132    /// We later check that there are no missing inputs in `apply_griffin_transaction`.
133    ///
134    /// The output includes the list of relevant UTxOs to be used for other
135    /// checks (in order to avoid a further db search).
136    fn validate_griffin_transaction(
137        transaction: &Transaction,
138    ) -> Result<ValidTransaction, UTxOError> {
139        debug!(
140            target: LOG_TARGET,
141            "validating griffin transaction",
142        );
143
144        // Make sure there are no duplicate inputs
145        {
146            let input_set: BTreeSet<_> = transaction
147                .transaction_body
148                .inputs
149                .iter()
150                .map(|o| o.encode())
151                .collect();
152            ensure!(
153                input_set.len() == transaction.transaction_body.inputs.len(),
154                UTxOError::Babbage(DuplicateInput)
155            );
156        }
157
158        let mut tx_outs_info: OutputInfoList = Vec::new();
159        let mut input_utxos: Vec<Output> = Vec::new();
160
161        // Add present inputs to a list to be used to produce the local UTxO set.
162        // Keep track of any missing inputs for use in the tagged transaction pool
163        let mut missing_inputs = Vec::new();
164        for input in transaction.transaction_body.inputs.iter() {
165            if let Some(u) = TransparentUtxoSet::peek_utxo(&input) {
166                tx_outs_info.push((
167                    hex::encode(u.address.0.as_slice()),
168                    PallasValue::from(u.clone().value),
169                    None, // irrelevant for phase 1 checks (always inline datum)
170                    None,
171                ));
172                // Repeated info in tx_outs_info, but we need this type for phase 2 checks
173                input_utxos.push(u);
174            } else {
175                missing_inputs.push(input.clone().encode());
176            }
177        }
178
179        // Make sure no outputs already exist in storage
180        let tx_hash = BlakeTwo256::hash_of(&transaction.encode());
181        for index in 0..transaction.transaction_body.outputs.len() {
182            let input = Input {
183                tx_hash,
184                index: index as u32,
185            };
186
187            debug!(
188                target: LOG_TARGET,
189                "Checking for pre-existing output {:?}", input
190            );
191
192            ensure!(
193                TransparentUtxoSet::peek_utxo(&input).is_none(),
194                UTxOError::Babbage(OutputAlreadyInUTxO)
195            );
196        }
197
198        // Griffin Tx -> Pallas Tx -> CBOR -> Minted Pallas Tx
199        // This last one is used to produce the local UTxO set.
200        let pallas_tx: PallasTransaction = <_>::from(transaction.clone());
201        let cbor_bytes: Vec<u8> = babbage_tx_to_cbor(&pallas_tx);
202        let mtx: MintedTx = babbage_minted_tx_from_cbor(&cbor_bytes);
203        let tx_body: &MintedTransactionBody = &mtx.transaction_body.clone();
204        let outs_info_clone = tx_outs_info.clone();
205        let utxos: UTxOs = mk_utxo_for_babbage_tx(tx_body, outs_info_clone.as_slice());
206
207        Self::pool_checks(&mtx, &utxos)?;
208
209        // Calculate the tx-pool tags provided by this transaction, which
210        // are just the encoded Inputs
211        let provides = (0..transaction.transaction_body.outputs.len())
212            .map(|i| {
213                let input = Input {
214                    tx_hash,
215                    index: i as u32,
216                };
217                input.encode()
218            })
219            .collect::<Vec<_>>();
220
221        // If any of the inputs are missing, we cannot make any more progress
222        if !missing_inputs.is_empty() {
223            debug!(
224                target: LOG_TARGET,
225                "Transaction is valid but still has missing inputs. Returning early.",
226            );
227            return Ok(ValidTransaction {
228                requires: missing_inputs,
229                provides,
230                priority: 0,
231                longevity: TransactionLongevity::MAX,
232                propagate: true,
233            });
234        }
235
236        // These checks were done in `apply_griffin_transaction`, but we do them here for simplicity.
237        // This might limit the ledger's ability to accept transactions that would be valid
238        // in a block, as in chaining.
239        Self::ledger_checks(&mtx, &utxos)?;
240        Self::phase_two_checks(&cbor_bytes, input_utxos)?;
241
242        // Return the valid transaction
243        Ok(ValidTransaction {
244            requires: Vec::new(),
245            provides,
246            priority: 0,
247            longevity: TransactionLongevity::MAX,
248            propagate: true,
249        })
250    }
251
252    /// Does full verification and application of griffin transactions.
253    /// Most of the validation happens in the call to `validate_griffin_transaction`.
254    /// Once those checks are done we make sure there are no missing inputs and then update storage.
255    fn apply_griffin_transaction(transaction: &Transaction) -> DispatchResult {
256        debug!(
257            target: LOG_TARGET,
258            "applying griffin transaction {:?}", transaction
259        );
260
261        // Re-do the pre-checks. These should have been done in the pool, but we can't
262        // guarantee that foreign nodes do these checks faithfully, so we need to check on-chain.
263        let valid_transaction = Self::validate_griffin_transaction(transaction)?;
264
265        // If there are still missing inputs, we cannot execute this,
266        // although it would be valid in the pool
267        ensure!(
268            valid_transaction.requires.is_empty(),
269            UTxOError::Babbage(InputNotInUTxO)
270        );
271
272        // At this point, all validation is complete, so we can commit the storage changes.
273        Self::update_storage(transaction);
274
275        Ok(())
276    }
277
278    /// Helper function to update the utxo set according to the given transaction.
279    /// This function does absolutely no validation. It assumes that the transaction
280    /// has already passed validation. Changes proposed by the transaction are written
281    /// blindly to storage.
282    fn update_storage(transaction: &Transaction) {
283        // Remove verified UTXOs
284        for input in &transaction.transaction_body.inputs {
285            TransparentUtxoSet::consume_utxo(input);
286        }
287
288        debug!(
289            target: LOG_TARGET,
290            "Transaction before updating storage {:?}", transaction
291        );
292        // Write the newly created utxos
293        for (index, output) in transaction.transaction_body.outputs.iter().enumerate() {
294            let input = Input {
295                tx_hash: BlakeTwo256::hash_of(&transaction.encode()),
296                index: index as u32,
297            };
298            TransparentUtxoSet::store_utxo(input, output);
299        }
300    }
301
302    /// A helper function that allows griffin runtimes to read the current block height
303    pub fn block_height() -> BlockNumber {
304        sp_io::storage::get(HEIGHT_KEY)
305            .and_then(|d| BlockNumber::decode(&mut &*d).ok())
306            .expect("A height is stored at the beginning of block one and never cleared.")
307    }
308
309    /// A helper function that allows griffin runtimes to read the start posix time of the first
310    /// block, in milliseconds
311    pub fn zero_time() -> u64 {
312        sp_io::storage::get(ZERO_TIME)
313            .and_then(|d| u64::decode(&mut &*d).ok())
314            .expect("Failed to read ZERO_TIME from storage.")
315    }
316
317    /// A helper function that allows griffin runtimes to read the slot number of the first block
318    pub fn zero_slot() -> u64 {
319        sp_io::storage::get(ZERO_SLOT)
320            .and_then(|d| u64::decode(&mut &*d).ok())
321            .expect("Failed to read ZERO_SLOT from storage.")
322    }
323
324    // These next three methods are for the block authoring workflow.
325    // Open the block, apply zero or more extrinsics, close the block
326
327    pub fn open_block(header: &Header) -> ExtrinsicInclusionMode {
328        debug!(
329            target: LOG_TARGET,
330            "Entering initialize_block. header: {:?}", header
331        );
332
333        // Store the transient partial header for updating at the end of the block.
334        // This will be removed from storage before the end of the block.
335        sp_io::storage::set(HEADER_KEY, &header.encode());
336
337        // Also store the height persistently so it is available when
338        // performing pool validations and other off-chain runtime calls.
339        sp_io::storage::set(HEIGHT_KEY, &header.number().encode());
340
341        // griffin blocks always allow user transactions.
342        ExtrinsicInclusionMode::AllExtrinsics
343    }
344
345    pub fn apply_extrinsic(extrinsic: Transaction) -> ApplyExtrinsicResult {
346        debug!(
347            target: LOG_TARGET,
348            "Entering apply_extrinsic: {:?}", extrinsic
349        );
350
351        // Append the current extrinsic to the transient list of extrinsics.
352        // This will be used when we calculate the extrinsics root at the end of the block.
353        let mut extrinsics = sp_io::storage::get(EXTRINSIC_KEY)
354            .and_then(|d| <Vec<Vec<u8>>>::decode(&mut &*d).ok())
355            .unwrap_or_default();
356        extrinsics.push(extrinsic.encode());
357        sp_io::storage::set(EXTRINSIC_KEY, &extrinsics.encode());
358
359        // Now actually apply the extrinsic
360        Self::apply_griffin_transaction(&extrinsic).map_err(|e| {
361            log::warn!(
362                target: LOG_TARGET,
363                "⛔ Griffin Transaction did not validate to be applied due to: {:?}",
364                e,
365            );
366            TransactionValidityError::Invalid(e.into())
367        })?;
368
369        Ok(Ok(()))
370    }
371
372    pub fn close_block() -> Header {
373        let mut header = sp_io::storage::get(HEADER_KEY)
374            .and_then(|d| Header::decode(&mut &*d).ok())
375            .expect("We initialized with header, it never got mutated, qed");
376
377        // the header itself contains the state root, so it cannot be inside the state (circular
378        // dependency..). Make sure in execute block path we have the same rule.
379        sp_io::storage::clear(HEADER_KEY);
380
381        let extrinsics = sp_io::storage::get(EXTRINSIC_KEY)
382            .and_then(|d| <Vec<Vec<u8>>>::decode(&mut &*d).ok())
383            .unwrap_or_default();
384        let extrinsics_root =
385            <Header as HeaderT>::Hashing::ordered_trie_root(extrinsics, StateVersion::V0);
386        sp_io::storage::clear(EXTRINSIC_KEY);
387        header.set_extrinsics_root(extrinsics_root);
388
389        let raw_state_root = &sp_io::storage::root(StateVersion::V1)[..];
390        let state_root = <Header as HeaderT>::Hash::decode(&mut &raw_state_root[..]).unwrap();
391        header.set_state_root(state_root);
392
393        debug!(target: LOG_TARGET, "finalizing block {:?}", header);
394        header
395    }
396
397    // This one is for the Core api. It is used to import blocks authored by foreign nodes.
398
399    pub fn execute_block(block: Block) {
400        debug!(
401            target: LOG_TARGET,
402            "Entering execute_block. block: {:?}", block
403        );
404
405        // Store the header. Although we don't need to mutate it, we do need to make
406        // info, such as the block height, available to individual pieces. This will
407        // be cleared before the end of the block
408        sp_io::storage::set(HEADER_KEY, &block.header().encode());
409
410        // Also store the height persistently so it is available when
411        // performing pool validations and other off-chain runtime calls.
412        sp_io::storage::set(HEIGHT_KEY, &block.header().number().encode());
413
414        // Apply each extrinsic
415        for extrinsic in block.extrinsics() {
416            match Self::apply_griffin_transaction(&extrinsic) {
417                Ok(()) => debug!(
418                    target: LOG_TARGET,
419                    "Successfully executed extrinsic: {:?}", extrinsic
420                ),
421                Err(e) => panic!("{:?}", e),
422            }
423        }
424
425        // Clear the transient header out of storage
426        sp_io::storage::clear(HEADER_KEY);
427
428        // Check state root
429        let raw_state_root = &sp_io::storage::root(StateVersion::V1)[..];
430        let state_root = <Header as HeaderT>::Hash::decode(&mut &raw_state_root[..]).unwrap();
431        assert_eq!(
432            *block.header().state_root(),
433            state_root,
434            "state root mismatch"
435        );
436
437        // Check extrinsics root.
438        let extrinsics = block
439            .extrinsics()
440            .iter()
441            .map(|x| x.encode())
442            .collect::<Vec<_>>();
443        let extrinsics_root =
444            <Header as HeaderT>::Hashing::ordered_trie_root(extrinsics, StateVersion::V0);
445        assert_eq!(
446            *block.header().extrinsics_root(),
447            extrinsics_root,
448            "extrinsics root mismatch"
449        );
450    }
451
452    // This one is the pool api. It is used to make preliminary checks in the transaction pool
453
454    pub fn validate_transaction(
455        source: TransactionSource,
456        tx: Transaction,
457        block_hash: <Block as BlockT>::Hash,
458    ) -> TransactionValidity {
459        debug!(
460            target: LOG_TARGET,
461            "Entering validate_transaction. source: {:?}, tx: {:?}, block hash: {:?}",
462            source,
463            tx,
464            block_hash
465        );
466
467        let r = Self::validate_griffin_transaction(&tx).map_err(|e| {
468            log::warn!(
469                target: LOG_TARGET,
470                "⛔ Griffin Transaction did not validate (in the pool): {:?}",
471                e,
472            );
473            TransactionValidityError::Invalid(e.into())
474        });
475
476        debug!(target: LOG_TARGET, "Validation result: {:?}", r);
477
478        r
479    }
480}