1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! This is a small pallet that handles runtime upgrades in chains that want
//! to support them.
//!
//! Right now this method is entirely unprotected (except by the verifier) which
//! may not be realistic enough for public production chains. It should be composed
//! with some governance mechanism when one is available.
//!
//! It is not possible to adhere perfectly to the UTXO model here, because the
//! wasm code must be stored in the well-known `:code` key. We stick as closely
//! as possible to the UTXO model by having a UTXO that holds a hash of the current
//! wasm code. Then we pass the full wasm code as part of the constraint checker and write
//! it to the well-known key as a side effect.

#![cfg_attr(not(feature = "std"), no_std)]

use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_runtime::transaction_validity::TransactionPriority;
use sp_std::vec::Vec;
use sp_storage::well_known_keys::CODE;
use tuxedo_core::{
    dynamic_typing::{DynamicallyTypedData, UtxoData},
    ensure, SimpleConstraintChecker,
};

#[cfg(test)]
mod tests;

/// A reference to a runtime wasm blob. It is just a hash.
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone)]
struct RuntimeRef {
    hash: [u8; 32],
}

impl UtxoData for RuntimeRef {
    const TYPE_ID: [u8; 4] = *b"upgd";
}

/// Reasons that the RuntimeUpgrade constraint checker may fail
#[derive(Debug)]
pub enum ConstraintCheckerError {
    // Again we're duplicating these common errors. Probably going to want a
    // better way to handle these.
    /// Wrong number of inputs were provided to the constraint checker.
    WrongNumberInputs,
    /// Wrong number of outputs were provided to the constraint checker.
    WrongNumberOutputs,
    /// An input data has the wrong type.
    BadlyTypedInput,
    /// An output data has the wrong type.
    BadlyTypedOutput,
    /// The Runtime Upgrade piece does not allow any evictions at all.
    NoEvictionsAllowed,

    // Now we get on to the actual upgrade-specific errors
    /// The consumed input does not match the current wasm. This should never happen
    /// and is indicative of inconsistent state. Perhaps another piece interfered?
    InputMismatch,
    /// The created output does not match the provided new runtime wasm.
    OutputMismatch,
}

/// The sole constraint checker for the runtime upgrade. It confirms that the UTXO
/// being consumed points to the correct current wasm and creates a new
/// UTXO for the new wasm.
///
/// This constraint checker is somewhat non-standard in that it has a side-effect that
/// writes the full wasm code to the well-known `:code` storage key. This is
/// necessary to satisfy Substrate's assumptions that this will happen.
#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
pub struct RuntimeUpgrade {
    full_wasm: Vec<u8>,
}

impl SimpleConstraintChecker for RuntimeUpgrade {
    type Error = ConstraintCheckerError;

    fn check(
        &self,
        input_data: &[DynamicallyTypedData],
        evicted_input_data: &[DynamicallyTypedData],
        _peeks: &[DynamicallyTypedData],
        output_data: &[DynamicallyTypedData],
    ) -> Result<TransactionPriority, Self::Error> {
        // Can't evict anything
        ensure!(
            evicted_input_data.is_empty(),
            ConstraintCheckerError::NoEvictionsAllowed
        );

        // Make sure there is a single input that matches the hash of the previous runtime logic
        ensure!(
            input_data.len() == 1,
            ConstraintCheckerError::WrongNumberInputs
        );
        let consumed = input_data[0]
            .extract::<RuntimeRef>()
            .map_err(|_| ConstraintCheckerError::BadlyTypedInput)?;
        let outgoing_runtime =
            sp_io::storage::get(CODE).expect("Some runtime code should always be stored");
        let outgoing_hash = sp_io::hashing::blake2_256(&outgoing_runtime);
        ensure!(
            consumed.hash == outgoing_hash,
            ConstraintCheckerError::InputMismatch
        );

        // Make sure there is a single output that matches the has of the incoming runtime logic
        ensure!(
            output_data.len() == 1,
            ConstraintCheckerError::WrongNumberOutputs
        );
        let created = output_data[0]
            .extract::<RuntimeRef>()
            .map_err(|_| ConstraintCheckerError::BadlyTypedOutput)?;
        let incoming_hash = sp_io::hashing::blake2_256(&self.full_wasm);
        ensure!(
            created.hash == incoming_hash,
            ConstraintCheckerError::OutputMismatch
        );

        // SIDE EFFECT: Write the new wasm to storage
        sp_io::storage::set(CODE, &self.full_wasm);

        //TODO Figure out a better priority
        Ok(0)
    }
}