Skip to content
This repository was archived by the owner on Feb 21, 2024. It is now read-only.

Commit 31520f3

Browse files
authored
Merge pull request paritytech#99 from subspace/refactor-verify-solution
Some refactorings to sc-consensus-subspace
2 parents 1e3f36d + 6427acf commit 31520f3

File tree

6 files changed

+896
-777
lines changed

6 files changed

+896
-777
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
// Copyright (C) 2021 Subspace Labs, Inc.
2+
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
3+
4+
// This program 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+
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
use super::*;
18+
19+
fn find_last_root_block<Block: BlockT, Client>(client: &Client) -> Option<RootBlock>
20+
where
21+
Client: ProvideRuntimeApi<Block> + BlockBackend<Block> + HeaderBackend<Block>,
22+
Client::Api: SubspaceApi<Block>,
23+
{
24+
let mut block_to_check = BlockId::Hash(client.info().best_hash);
25+
loop {
26+
let block = client
27+
.block(&block_to_check)
28+
.expect("Older blocks should always exist")
29+
.expect("Older blocks should always exist");
30+
let mut latest_root_block: Option<RootBlock> = None;
31+
32+
for extrinsic in block.block.extrinsics() {
33+
match client
34+
.runtime_api()
35+
.extract_root_block(&block_to_check, extrinsic.encode())
36+
{
37+
Ok(Some(root_block)) => match &mut latest_root_block {
38+
Some(latest_root_block) => {
39+
if latest_root_block.segment_index() < root_block.segment_index() {
40+
*latest_root_block = root_block;
41+
}
42+
}
43+
None => {
44+
latest_root_block.replace(root_block);
45+
}
46+
},
47+
Ok(None) => {
48+
// Some other extrinsic, ignore
49+
}
50+
Err(error) => {
51+
// TODO: Probably light client, can this even happen?
52+
error!(target: "subspace", "Failed to make runtime API call: {:?}", error);
53+
}
54+
}
55+
}
56+
57+
if latest_root_block.is_some() {
58+
break latest_root_block;
59+
}
60+
61+
let parent_block_hash = *block.block.header().parent_hash();
62+
63+
if parent_block_hash == Block::Hash::default() {
64+
// Genesis block, nothing else to check
65+
break None;
66+
}
67+
68+
block_to_check = BlockId::Hash(parent_block_hash);
69+
}
70+
}
71+
72+
/// Start an archiver that will listen for imported blocks and archive blocks at `K` depth,
73+
/// producing pieces and root blocks (root blocks are then added back to the blockchain as
74+
/// `store_root_block` extrinsic).
75+
pub fn start_subspace_archiver<Block: BlockT, Client>(
76+
subspace_link: &SubspaceLink<Block>,
77+
client: Arc<Client>,
78+
spawner: &impl sp_core::traits::SpawnNamed,
79+
) where
80+
Client: ProvideRuntimeApi<Block>
81+
+ BlockBackend<Block>
82+
+ HeaderBackend<Block>
83+
+ Send
84+
+ Sync
85+
+ 'static,
86+
Client::Api: SubspaceApi<Block>,
87+
{
88+
let genesis_block_id = BlockId::Number(Zero::zero());
89+
90+
let confirmation_depth_k = client
91+
.runtime_api()
92+
.confirmation_depth_k(&genesis_block_id)
93+
.expect("Failed to get `confirmation_depth_k` from runtime API");
94+
let record_size = client
95+
.runtime_api()
96+
.record_size(&genesis_block_id)
97+
.expect("Failed to get `record_size` from runtime API");
98+
let recorded_history_segment_size = client
99+
.runtime_api()
100+
.recorded_history_segment_size(&genesis_block_id)
101+
.expect("Failed to get `recorded_history_segment_size` from runtime API");
102+
103+
let mut archiver = if let Some(last_root_block) = find_last_root_block(client.as_ref()) {
104+
// Continuing from existing initial state
105+
let last_archived_block_number = last_root_block.last_archived_block().number;
106+
info!(
107+
target: "subspace",
108+
"Last archived block {}",
109+
last_archived_block_number,
110+
);
111+
let last_archived_block = client
112+
.block(&BlockId::Number(last_archived_block_number.into()))
113+
.expect("Older blocks should always exist")
114+
.expect("Older blocks should always exist");
115+
116+
let block_object_mapping = client
117+
.runtime_api()
118+
.extract_block_object_mapping(
119+
&BlockId::Number(last_archived_block_number.saturating_sub(1).into()),
120+
last_archived_block.block.clone(),
121+
)
122+
.expect("Must be able to make runtime call");
123+
124+
BlockArchiver::with_initial_state(
125+
record_size as usize,
126+
recorded_history_segment_size as usize,
127+
last_root_block,
128+
&last_archived_block.encode(),
129+
block_object_mapping,
130+
)
131+
.expect("Incorrect parameters for archiver")
132+
} else {
133+
// Starting from genesis
134+
let runtime_api = client.runtime_api();
135+
136+
let mut object_archiver =
137+
ObjectArchiver::new(record_size as usize, recorded_history_segment_size as usize)
138+
.expect("Incorrect parameters for archiver");
139+
140+
let pre_genesis_object_size = runtime_api
141+
.pre_genesis_object_size(&genesis_block_id)
142+
.expect("Failed to get `pre_genesis_object_size` from runtime API");
143+
let pre_genesis_object_count = runtime_api
144+
.pre_genesis_object_count(&genesis_block_id)
145+
.expect("Failed to get `pre_genesis_object_count` from runtime API");
146+
let pre_genesis_object_seed = runtime_api
147+
.pre_genesis_object_seed(&genesis_block_id)
148+
.expect("Failed to get `pre_genesis_object_seed` from runtime API");
149+
150+
// These archived segments are a part of the public parameters of network setup, thus do not
151+
// need to be sent to farmers
152+
info!(target: "subspace", "Processing pre-genesis objects");
153+
let new_root_blocks: Vec<RootBlock> = (0..pre_genesis_object_count)
154+
.map(|index| {
155+
object_archiver
156+
.add_object(pre_genesis_data::from_seed(
157+
&pre_genesis_object_seed,
158+
index,
159+
pre_genesis_object_size,
160+
))
161+
.into_iter()
162+
})
163+
.flatten()
164+
.map(|archived_segment| archived_segment.root_block)
165+
.collect();
166+
167+
if subspace_link.authoring_enabled {
168+
// TODO: Block size will not be enough in case number of root blocks it too large; not a
169+
// problem for now though. Also RAM usage will be very significant with current approach.
170+
// Submit store root block extrinsic at genesis block.
171+
for root_block in new_root_blocks.iter().copied() {
172+
client
173+
.runtime_api()
174+
.submit_store_root_block_extrinsic(&genesis_block_id, root_block)
175+
.expect("Failed to submit `store_root_block` extrinsic at genesis block");
176+
}
177+
}
178+
179+
// Set list of expected root blocks for the next block after genesis (we can't have
180+
// extrinsics in genesis block, at least not right now)
181+
subspace_link
182+
.root_blocks
183+
.lock()
184+
.put(One::one(), new_root_blocks);
185+
186+
info!(target: "subspace", "Finished processing pre-genesis objects");
187+
188+
object_archiver.into_block_archiver()
189+
};
190+
191+
// Process blocks since last fully archived block (or genesis) up to the current head minus K
192+
{
193+
let blocks_to_archive_from = archiver
194+
.last_archived_block_number()
195+
.map(|n| n + 1)
196+
.unwrap_or_default();
197+
let best_number = client.info().best_number;
198+
let blocks_to_archive_to = TryInto::<u32>::try_into(best_number)
199+
.unwrap_or_else(|_| {
200+
panic!(
201+
"Best block number {} can't be converted into u32",
202+
best_number,
203+
);
204+
})
205+
.checked_sub(confirmation_depth_k);
206+
207+
if let Some(blocks_to_archive_to) = blocks_to_archive_to {
208+
info!(
209+
target: "subspace",
210+
"Archiving already produced blocks {}..={}",
211+
blocks_to_archive_from,
212+
blocks_to_archive_to,
213+
);
214+
for block_to_archive in blocks_to_archive_from..=blocks_to_archive_to {
215+
let block = client
216+
.block(&BlockId::Number(block_to_archive.into()))
217+
.expect("Older block by number must always exist")
218+
.expect("Older block by number must always exist");
219+
220+
let block_object_mapping = client
221+
.runtime_api()
222+
.extract_block_object_mapping(
223+
&BlockId::Number(block_to_archive.saturating_sub(1).into()),
224+
block.block.clone(),
225+
)
226+
.expect("Must be able to make runtime call");
227+
228+
// These archived segments were processed before, thus do not need to be sent to
229+
// farmers
230+
let new_root_blocks = archiver
231+
.add_block(block.encode(), block_object_mapping)
232+
.into_iter()
233+
.map(|archived_segment| archived_segment.root_block)
234+
.collect();
235+
236+
// Set list of expected root blocks for the block where we expect root block
237+
// extrinsic to be included
238+
subspace_link.root_blocks.lock().put(
239+
(block_to_archive + confirmation_depth_k + 1).into(),
240+
new_root_blocks,
241+
);
242+
}
243+
}
244+
}
245+
246+
spawner.spawn_blocking(
247+
"subspace-archiver",
248+
Box::pin({
249+
let mut imported_block_notification_stream =
250+
subspace_link.imported_block_notification_stream.subscribe();
251+
let archived_segment_notification_sender =
252+
subspace_link.archived_segment_notification_sender.clone();
253+
254+
async move {
255+
let mut last_archived_block_number =
256+
archiver.last_archived_block_number().map(Into::into);
257+
258+
while let Some((block_number, mut root_block_sender)) =
259+
imported_block_notification_stream.next().await
260+
{
261+
let block_to_archive =
262+
match block_number.checked_sub(&confirmation_depth_k.into()) {
263+
Some(block_to_archive) => block_to_archive,
264+
None => {
265+
continue;
266+
}
267+
};
268+
269+
if let Some(last_archived_block) = &mut last_archived_block_number {
270+
if *last_archived_block >= block_to_archive {
271+
// This block was already archived, skip
272+
continue;
273+
}
274+
275+
*last_archived_block = block_to_archive;
276+
} else {
277+
last_archived_block_number.replace(block_to_archive);
278+
}
279+
280+
debug!(target: "subspace", "Archiving block {:?}", block_to_archive);
281+
282+
let block = client
283+
.block(&BlockId::Number(block_to_archive))
284+
.expect("Older block by number must always exist")
285+
.expect("Older block by number must always exist");
286+
287+
let block_object_mapping = client
288+
.runtime_api()
289+
.extract_block_object_mapping(
290+
&BlockId::Number(block_to_archive.saturating_sub(One::one())),
291+
block.block.clone(),
292+
)
293+
.expect("Must be able to make runtime call");
294+
295+
for archived_segment in archiver.add_block(block.encode(), block_object_mapping)
296+
{
297+
let ArchivedSegment {
298+
root_block,
299+
pieces,
300+
object_mapping,
301+
} = archived_segment;
302+
303+
archived_segment_notification_sender.notify(move || {
304+
ArchivedSegmentNotification {
305+
root_block,
306+
pieces,
307+
object_mapping,
308+
}
309+
});
310+
311+
let _ = root_block_sender.send(root_block).await;
312+
}
313+
}
314+
}
315+
}),
316+
);
317+
}

0 commit comments

Comments
 (0)