1use futures::{
20 prelude::*,
21 task::{Context, Poll},
22};
23use futures_timer::Delay;
24use log::*;
25use parking_lot::Mutex;
26use sc_client_api::ImportNotifications;
27use sc_consensus::{BlockImportParams, BoxBlockImport, StateAction, StorageChanges};
28use sp_consensus::{BlockOrigin, Proposal};
29use sp_runtime::{
30 generic::BlockId,
31 traits::{Block as BlockT, Header as HeaderT},
32 DigestItem,
33};
34use std::{
35 pin::Pin,
36 sync::{
37 atomic::{AtomicUsize, Ordering},
38 Arc,
39 },
40 time::Duration,
41};
42
43use crate::{PowAlgorithm, PowIntermediate, Seal, INTERMEDIATE_KEY, LOG_TARGET, POW_ENGINE_ID};
44
45#[derive(Clone, Eq, PartialEq)]
47pub struct MiningMetadata<H, D> {
48 pub best_hash: H,
50 pub pre_hash: H,
52 pub pre_runtime: Option<Vec<u8>>,
54 pub difficulty: D,
56}
57
58pub struct MiningBuild<Block: BlockT, Algorithm: PowAlgorithm<Block>> {
60 pub metadata: MiningMetadata<Block::Hash, Algorithm::Difficulty>,
62 pub proposal: Proposal<Block>,
64}
65
66#[derive(Eq, PartialEq, Clone, Copy)]
68pub struct Version(usize);
69
70pub struct MiningHandle<
72 Block: BlockT,
73 Algorithm: PowAlgorithm<Block>,
74 L: sc_consensus::JustificationSyncLink<Block>,
75> {
76 version: Arc<AtomicUsize>,
77 algorithm: Arc<Algorithm>,
78 justification_sync_link: Arc<L>,
79 build: Arc<Mutex<Option<MiningBuild<Block, Algorithm>>>>,
80 block_import: Arc<Mutex<BoxBlockImport<Block>>>,
81}
82
83impl<Block, Algorithm, L> MiningHandle<Block, Algorithm, L>
84where
85 Block: BlockT,
86 Algorithm: PowAlgorithm<Block>,
87 Algorithm::Difficulty: 'static + Send,
88 L: sc_consensus::JustificationSyncLink<Block>,
89{
90 fn increment_version(&self) {
91 self.version.fetch_add(1, Ordering::SeqCst);
92 }
93
94 pub(crate) fn new(
95 algorithm: Algorithm,
96 block_import: BoxBlockImport<Block>,
97 justification_sync_link: L,
98 ) -> Self {
99 Self {
100 version: Arc::new(AtomicUsize::new(0)),
101 algorithm: Arc::new(algorithm),
102 justification_sync_link: Arc::new(justification_sync_link),
103 build: Arc::new(Mutex::new(None)),
104 block_import: Arc::new(Mutex::new(block_import)),
105 }
106 }
107
108 pub(crate) fn on_major_syncing(&self) {
109 let mut build = self.build.lock();
110 *build = None;
111 self.increment_version();
112 }
113
114 pub(crate) fn on_build(&self, value: MiningBuild<Block, Algorithm>) {
115 let mut build = self.build.lock();
116 *build = Some(value);
117 self.increment_version();
118 }
119
120 pub fn version(&self) -> Version {
125 Version(self.version.load(Ordering::SeqCst))
126 }
127
128 pub fn best_hash(&self) -> Option<Block::Hash> {
131 self.build.lock().as_ref().map(|b| b.metadata.best_hash)
132 }
133
134 pub fn metadata(&self) -> Option<MiningMetadata<Block::Hash, Algorithm::Difficulty>> {
136 self.build.lock().as_ref().map(|b| b.metadata.clone())
137 }
138
139 pub async fn submit(&self, seal: Seal) -> bool {
142 if let Some(metadata) = self.metadata() {
143 match self.algorithm.verify(
144 &BlockId::Hash(metadata.best_hash),
145 &metadata.pre_hash,
146 metadata.pre_runtime.as_ref().map(|v| &v[..]),
147 &seal,
148 metadata.difficulty,
149 ) {
150 Ok(true) => (),
151 Ok(false) => {
152 warn!(target: LOG_TARGET, "Unable to import mined block: seal is invalid",);
153 return false
154 },
155 Err(err) => {
156 warn!(target: LOG_TARGET, "Unable to import mined block: {}", err,);
157 return false
158 },
159 }
160 } else {
161 warn!(target: LOG_TARGET, "Unable to import mined block: metadata does not exist",);
162 return false
163 }
164
165 let build = if let Some(build) = {
166 let mut build = self.build.lock();
167 let value = build.take();
168 if value.is_some() {
169 self.increment_version();
170 }
171 value
172 } {
173 build
174 } else {
175 warn!(target: LOG_TARGET, "Unable to import mined block: build does not exist",);
176 return false
177 };
178
179 let seal = DigestItem::Seal(POW_ENGINE_ID, seal);
180 let (header, body) = build.proposal.block.deconstruct();
181
182 let mut import_block = BlockImportParams::new(BlockOrigin::Own, header);
183 import_block.post_digests.push(seal);
184 import_block.body = Some(body);
185 import_block.state_action =
186 StateAction::ApplyChanges(StorageChanges::Changes(build.proposal.storage_changes));
187
188 let intermediate = PowIntermediate::<Algorithm::Difficulty> {
189 difficulty: Some(build.metadata.difficulty),
190 };
191 import_block.insert_intermediate(INTERMEDIATE_KEY, intermediate);
192
193 let header = import_block.post_header();
194 let block_import = self.block_import.lock();
195
196 match block_import.import_block(import_block).await {
197 Ok(res) => {
198 res.handle_justification(
199 &header.hash(),
200 *header.number(),
201 &self.justification_sync_link,
202 );
203
204 info!(
205 target: LOG_TARGET,
206 "✅ Successfully mined block on top of: {}", build.metadata.best_hash
207 );
208 true
209 },
210 Err(err) => {
211 warn!(target: LOG_TARGET, "Unable to import mined block: {}", err,);
212 false
213 },
214 }
215 }
216}
217
218impl<Block, Algorithm, L> Clone for MiningHandle<Block, Algorithm, L>
219where
220 Block: BlockT,
221 Algorithm: PowAlgorithm<Block>,
222 L: sc_consensus::JustificationSyncLink<Block>,
223{
224 fn clone(&self) -> Self {
225 Self {
226 version: self.version.clone(),
227 algorithm: self.algorithm.clone(),
228 justification_sync_link: self.justification_sync_link.clone(),
229 build: self.build.clone(),
230 block_import: self.block_import.clone(),
231 }
232 }
233}
234
235pub struct UntilImportedOrTimeout<Block: BlockT> {
237 import_notifications: ImportNotifications<Block>,
238 timeout: Duration,
239 inner_delay: Option<Delay>,
240}
241
242impl<Block: BlockT> UntilImportedOrTimeout<Block> {
243 pub fn new(import_notifications: ImportNotifications<Block>, timeout: Duration) -> Self {
245 Self { import_notifications, timeout, inner_delay: None }
246 }
247}
248
249impl<Block: BlockT> Stream for UntilImportedOrTimeout<Block> {
250 type Item = ();
251
252 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<()>> {
253 let mut fire = false;
254
255 loop {
256 match Stream::poll_next(Pin::new(&mut self.import_notifications), cx) {
257 Poll::Pending => break,
258 Poll::Ready(Some(_)) => {
259 fire = true;
260 },
261 Poll::Ready(None) => return Poll::Ready(None),
262 }
263 }
264
265 let timeout = self.timeout;
266 let inner_delay = self.inner_delay.get_or_insert_with(|| Delay::new(timeout));
267
268 match Future::poll(Pin::new(inner_delay), cx) {
269 Poll::Pending => (),
270 Poll::Ready(()) => {
271 fire = true;
272 },
273 }
274
275 if fire {
276 self.inner_delay = None;
277 Poll::Ready(Some(()))
278 } else {
279 Poll::Pending
280 }
281 }
282}