1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
#![cfg_attr(not(feature = "std"), no_std)]
18
use frame_system::offchain::CreateInherent;
19
use frame_system::offchain::CreateSignedTransaction;
20
use {
21
    frame_system::{
22
        self as system, ensure_none, ensure_root, offchain::SubmitTransaction,
23
        pallet_prelude::BlockNumberFor,
24
    },
25
    sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
26
};
27

            
28
pub use pallet::*;
29
#[frame_support::pallet]
30
pub mod pallet {
31
    use {super::*, frame_support::pallet_prelude::*, frame_system::pallet_prelude::*};
32

            
33
1666
    #[pallet::pallet]
34
    pub struct Pallet<T>(_);
35

            
36
    #[pallet::config]
37
    pub trait Config:
38
        CreateSignedTransaction<Call<Self>> + CreateInherent<Call<Self>> + frame_system::Config
39
    {
40
        /// The overarching event type.
41
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
42

            
43
        /// Number of blocks of cooldown after unsigned transaction is included.
44
        ///
45
        /// This ensures that we only accept unsigned transactions once, every `UnsignedInterval`
46
        /// blocks.
47
        #[pallet::constant]
48
        type UnsignedInterval: Get<BlockNumberFor<Self>>;
49
    }
50

            
51
    #[pallet::storage]
52
    pub(super) type OffchainWorkerTestEnabled<T> = StorageValue<_, bool, ValueQuery>;
53

            
54
    /// Defines the block when next unsigned transaction will be accepted.
55
    ///
56
    /// To prevent spam of unsigned (and unpaid!) transactions on the network,
57
    /// we only allow one transaction every `T::UnsignedInterval` blocks.
58
    /// This storage entry defines when new transaction is going to be accepted.
59
    #[pallet::storage]
60
    pub(super) type NextUnsignedAt<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
61

            
62
    #[pallet::genesis_config]
63
    pub struct GenesisConfig<T: Config> {
64
        pub _phantom_data: PhantomData<T>,
65
    }
66

            
67
    #[pallet::genesis_build]
68
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
69
        fn build(&self) {
70
            <OffchainWorkerTestEnabled<T>>::put(&false);
71
        }
72
    }
73

            
74
86
    #[pallet::hooks]
75
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
76
        /// Offchain worker entry point.
77
        ///
78
        /// By implementing `fn offchain_worker` you declare a new offchain worker.
79
        /// This function will be called when the node is fully synced and a new best block is
80
        /// successfully imported.
81
        /// Note that it's not guaranteed for offchain workers to run on EVERY block, there might
82
        /// be cases where some blocks are skipped, or for some the worker runs twice (re-orgs),
83
        /// so the code should be able to handle that.
84
        fn offchain_worker(block_number: BlockNumberFor<T>) {
85
            log::info!("Entering off-chain worker.");
86
            // The entry point of your code called by off-chain worker
87
            let res = Self::send_raw_unsigned_transaction(block_number);
88
            if let Err(e) = res {
89
                log::error!("Error: {}", e);
90
            }
91
        }
92
    }
93
    #[pallet::call]
94
    impl<T: Config> Pallet<T> {
95
        /// Switches on or off the offchain worker
96
        ///
97
        /// Only root (or specified authority account) should be able to switch
98
        /// the off-chain worker on and off to avoid enabling it by default in production
99
        #[pallet::call_index(0)]
100
        #[pallet::weight(T::DbWeight::get().write)]
101
        #[allow(clippy::useless_conversion)]
102
        pub fn set_offchain_worker(
103
            origin: OriginFor<T>,
104
            is_testing_enabled: bool,
105
        ) -> DispatchResultWithPostInfo {
106
            ensure_root(origin)?;
107

            
108
            OffchainWorkerTestEnabled::<T>::put(is_testing_enabled);
109
            Ok(().into())
110
        }
111

            
112
        /// Submits unsigned transaction that emits an event
113
        ///
114
        /// Can be triggered only by an offchain worker
115
        #[pallet::call_index(1)]
116
        #[pallet::weight(T::DbWeight::get().write)]
117
        #[allow(clippy::useless_conversion)]
118
        pub fn submit_event_unsigned(
119
            origin: OriginFor<T>,
120
            _block_number: BlockNumberFor<T>,
121
        ) -> DispatchResultWithPostInfo {
122
            // This ensures that the function can only be called via unsigned transaction.
123
            ensure_none(origin)?;
124

            
125
            ensure!(
126
                OffchainWorkerTestEnabled::<T>::get(),
127
                Error::<T>::OffchainWorkerNotEnabled,
128
            );
129

            
130
            // Increment the block number at which we expect next unsigned transaction.
131
            let current_block = <frame_system::Pallet<T>>::block_number();
132

            
133
            // Emits offchain event
134
            Self::deposit_event(Event::SimpleOffchainEvent);
135

            
136
            <NextUnsignedAt<T>>::put(current_block + T::UnsignedInterval::get());
137
            Ok(().into())
138
        }
139
    }
140

            
141
    /// Events for the pallet.
142
    #[pallet::event]
143
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
144
    pub enum Event<T: Config> {
145
        /// Simple offchain event
146
        SimpleOffchainEvent,
147
    }
148

            
149
    #[pallet::error]
150
    pub enum Error<T> {
151
        OffchainWorkerNotEnabled,
152
    }
153

            
154
    #[pallet::validate_unsigned]
155
    impl<T: Config> ValidateUnsigned for Pallet<T> {
156
        type Call = Call<T>;
157

            
158
        /// Validate unsigned call to this module.
159
        ///
160
        /// By default unsigned transactions are disallowed, but implementing the validator
161
        /// here we make sure that some particular calls (the ones produced by offchain worker)
162
        /// are being whitelisted and marked as valid.
163
        fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
164
            if let Call::submit_event_unsigned { block_number } = call {
165
                Self::validate_transaction_parameters(block_number)
166
            } else {
167
                InvalidTransaction::Call.into()
168
            }
169
        }
170
    }
171
}
172

            
173
impl<T: Config> Pallet<T> {
174
    /// A helper function to sign payload and send an unsigned transaction
175
    fn send_raw_unsigned_transaction(block_number: BlockNumberFor<T>) -> Result<(), &'static str> {
176
        // Make sure offchain worker testing is enabled
177
        let is_offchain_worker_enabled = OffchainWorkerTestEnabled::<T>::get();
178
        if !is_offchain_worker_enabled {
179
            return Err("Offchain worker is not enabled");
180
        }
181
        // Make sure transaction can be sent
182
        let next_unsigned_at = NextUnsignedAt::<T>::get();
183
        if next_unsigned_at > block_number {
184
            return Err("Too early to send unsigned transaction");
185
        }
186

            
187
        let call = Call::submit_event_unsigned { block_number };
188

            
189
        let xt = T::create_inherent(call.into());
190
        SubmitTransaction::<T, Call<T>>::submit_transaction(xt)
191
            .map_err(|()| "Unable to submit unsigned transaction.")?;
192

            
193
        Ok(())
194
    }
195

            
196
    fn validate_transaction_parameters(block_number: &BlockNumberFor<T>) -> TransactionValidity {
197
        // Make sure offchain worker testing is enabled
198
        let is_offchain_worker_enabled = OffchainWorkerTestEnabled::<T>::get();
199
        if !is_offchain_worker_enabled {
200
            return InvalidTransaction::Call.into();
201
        }
202
        // Now let's check if the transaction has any chance to succeed.
203
        let next_unsigned_at = NextUnsignedAt::<T>::get();
204
        if &next_unsigned_at > block_number {
205
            return InvalidTransaction::Stale.into();
206
        }
207
        // Let's make sure to reject transactions from the future.
208
        let current_block = <system::Pallet<T>>::block_number();
209
        if &current_block < block_number {
210
            return InvalidTransaction::Future.into();
211
        }
212
        ValidTransaction::with_tag_prefix("ExampleOffchainWorker")
213
            // We set base priority to 2**20 and hope it's included before any other
214
            // transactions in the pool. Next we tweak the priority depending on how much
215
            // it differs from the current average. (the more it differs the more priority it
216
            // has).
217
            .priority(2u64.pow(20))
218
            // This transaction does not require anything else to go before into the pool.
219
            // In theory we could require `previous_unsigned_at` transaction to go first,
220
            // but it's not necessary in our case.
221
            //.and_requires()
222
            // We set the `provides` tag to be the same as `next_unsigned_at`. This makes
223
            // sure only one transaction produced after `next_unsigned_at` will ever
224
            // get to the transaction pool and will end up in the block.
225
            // We can still have multiple transactions compete for the same "spot",
226
            // and the one with higher priority will replace other one in the pool.
227
            .and_provides(next_unsigned_at)
228
            // The transaction is only valid for next 5 blocks. After that it's
229
            // going to be revalidated by the pool.
230
            .longevity(6)
231
            // It's fine to propagate that transaction to other peers, which means it can be
232
            // created even by nodes that don't produce blocks.
233
            // Note that sometimes it's better to keep it for yourself (if you are the block
234
            // producer), since for instance in some schemes others may copy your solution and
235
            // claim a reward.
236
            .propagate(true)
237
            .build()
238
    }
239
}