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