subspace_test_client/
lib.rs

1// Copyright (C) 2021 Subspace Labs, Inc.
2// SPDX-License-Identifier: GPL-3.0-or-later
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//! Subspace test client only.
18
19#![warn(unused_crate_dependencies)]
20
21pub mod auto_id_domain_chain_spec;
22pub mod chain_spec;
23pub mod evm_domain_chain_spec;
24
25use futures::StreamExt;
26use futures::executor::block_on;
27use sc_client_api::{BlockBackend, HeaderBackend};
28use sc_consensus_subspace::archiver::encode_block;
29use sc_consensus_subspace::notification::SubspaceNotificationStream;
30use sc_consensus_subspace::slot_worker::{NewSlotNotification, RewardSigningNotification};
31use sp_api::ProvideRuntimeApi;
32use sp_consensus_subspace::SubspaceApi;
33use sp_core::{Decode, Encode};
34use std::num::{NonZeroU64, NonZeroUsize};
35use std::slice;
36use std::sync::Arc;
37use subspace_core_primitives::objects::BlockObjectMapping;
38use subspace_core_primitives::pieces::Record;
39use subspace_core_primitives::pos::PosSeed;
40use subspace_core_primitives::segments::{HistorySize, SegmentIndex};
41use subspace_core_primitives::solutions::{RewardSignature, Solution};
42use subspace_core_primitives::{PublicKey, REWARD_SIGNING_CONTEXT};
43use subspace_erasure_coding::ErasureCoding;
44use subspace_farmer_components::FarmerProtocolInfo;
45use subspace_farmer_components::auditing::audit_sector_sync;
46use subspace_farmer_components::plotting::{
47    CpuRecordsEncoder, PlotSectorOptions, PlottedSector, plot_sector,
48};
49use subspace_farmer_components::reading::ReadSectorRecordChunksMode;
50use subspace_kzg::Kzg;
51use subspace_proof_of_space::{Table, TableGenerator};
52use subspace_runtime_primitives::opaque::Block;
53use subspace_service::{FullClient, NewFull};
54use zeroize::Zeroizing;
55
56// Smaller value for testing purposes
57const MAX_PIECES_IN_SECTOR: u16 = 32;
58
59/// The client type being used by the test service.
60pub type Client = FullClient<subspace_test_runtime::RuntimeApi>;
61
62/// The backend type being used by the test service.
63pub type Backend = sc_service::TFullBackend<Block>;
64
65/// Run a farmer.
66pub fn start_farmer<PosTable>(new_full: &NewFull<Client>)
67where
68    PosTable: Table,
69{
70    let client = new_full.client.clone();
71    let new_slot_notification_stream = new_full.new_slot_notification_stream.clone();
72    let reward_signing_notification_stream = new_full.reward_signing_notification_stream.clone();
73
74    let keypair = schnorrkel::Keypair::generate();
75    let subspace_farming =
76        start_farming::<PosTable, _>(keypair.clone(), client, new_slot_notification_stream);
77    new_full
78        .task_manager
79        .spawn_essential_handle()
80        .spawn_blocking("subspace-farmer", Some("farming"), subspace_farming);
81
82    new_full
83        .task_manager
84        .spawn_essential_handle()
85        .spawn_blocking("subspace-farmer", Some("block-signing"), async move {
86            let substrate_ctx = schnorrkel::context::signing_context(REWARD_SIGNING_CONTEXT);
87            let signing_pair: Zeroizing<schnorrkel::Keypair> = Zeroizing::new(keypair);
88
89            let mut reward_signing_notification_stream =
90                reward_signing_notification_stream.subscribe();
91
92            while let Some(RewardSigningNotification {
93                hash: header_hash,
94                signature_sender,
95                ..
96            }) = reward_signing_notification_stream.next().await
97            {
98                let header_hash: [u8; 32] = header_hash.into();
99                let signature = RewardSignature::from(
100                    signing_pair
101                        .sign(substrate_ctx.bytes(&header_hash))
102                        .to_bytes(),
103                );
104                signature_sender.unbounded_send(signature).unwrap();
105            }
106        });
107}
108
109async fn start_farming<PosTable, Client>(
110    keypair: schnorrkel::Keypair,
111    client: Arc<Client>,
112    new_slot_notification_stream: SubspaceNotificationStream<NewSlotNotification>,
113) where
114    PosTable: Table,
115    Client: ProvideRuntimeApi<Block>
116        + BlockBackend<Block>
117        + HeaderBackend<Block>
118        + Send
119        + Sync
120        + 'static,
121    Client::Api: SubspaceApi<Block, PublicKey>,
122{
123    let (plotting_result_sender, plotting_result_receiver) = futures::channel::oneshot::channel();
124
125    let kzg = Kzg::new();
126    let erasure_coding = ErasureCoding::new(
127        NonZeroUsize::new(Record::NUM_S_BUCKETS.next_power_of_two().ilog2() as usize)
128            .expect("Not zero; qed"),
129    )
130    .unwrap();
131
132    let table_generator = PosTable::generator();
133
134    std::thread::spawn({
135        let keypair = keypair.clone();
136        let erasure_coding = erasure_coding.clone();
137
138        move || {
139            let (sector, sector_metadata, table_generator) =
140                block_on(plot_one_segment::<PosTable, _>(
141                    client.as_ref(),
142                    &keypair,
143                    MAX_PIECES_IN_SECTOR,
144                    &erasure_coding,
145                    table_generator,
146                ));
147            plotting_result_sender
148                .send((sector, sector_metadata, table_generator))
149                .unwrap();
150        }
151    });
152
153    let (sector, plotted_sector, mut table_generator) = plotting_result_receiver.await.unwrap();
154    let public_key = PublicKey::from(keypair.public.to_bytes());
155
156    let mut new_slot_notification_stream = new_slot_notification_stream.subscribe();
157
158    while let Some(NewSlotNotification {
159        new_slot_info,
160        mut solution_sender,
161    }) = new_slot_notification_stream.next().await
162    {
163        if u64::from(new_slot_info.slot) % 2 == 0 {
164            let global_challenge = new_slot_info
165                .proof_of_time
166                .derive_global_randomness()
167                .derive_global_challenge(new_slot_info.slot.into());
168            let audit_result = audit_sector_sync(
169                &public_key,
170                &global_challenge,
171                new_slot_info.solution_range,
172                &sector,
173                &plotted_sector.sector_metadata,
174            );
175
176            let solution = audit_result
177                .unwrap()
178                .unwrap()
179                .solution_candidates
180                .into_solutions(
181                    &public_key,
182                    &kzg,
183                    &erasure_coding,
184                    ReadSectorRecordChunksMode::ConcurrentChunks,
185                    |seed: &PosSeed| table_generator.generate_parallel(seed),
186                )
187                .unwrap()
188                .next()
189                .expect("With max solution range there must be a solution; qed")
190                .unwrap();
191            // Lazy conversion to a different type of public key and reward address
192            let solution = Solution::decode(&mut solution.encode().as_slice()).unwrap();
193            let _ = solution_sender.try_send(solution);
194        }
195    }
196}
197
198async fn plot_one_segment<PosTable, Client>(
199    client: &Client,
200    keypair: &schnorrkel::Keypair,
201    pieces_in_sector: u16,
202    erasure_coding: &ErasureCoding,
203    mut table_generator: PosTable::Generator,
204) -> (Vec<u8>, PlottedSector, PosTable::Generator)
205where
206    PosTable: Table,
207    Client: BlockBackend<Block> + HeaderBackend<Block>,
208{
209    let kzg = Kzg::new();
210    let mut archiver =
211        subspace_archiving::archiver::Archiver::new(kzg.clone(), erasure_coding.clone());
212
213    let genesis_block = client.block(client.info().genesis_hash).unwrap().unwrap();
214    let archived_segment = archiver
215        .add_block(
216            encode_block(genesis_block),
217            BlockObjectMapping::default(),
218            true,
219        )
220        .archived_segments
221        .into_iter()
222        .next()
223        .expect("First block is always producing one segment; qed");
224    let history_size = HistorySize::from(SegmentIndex::ZERO);
225    let mut sector = Vec::new();
226    let sector_index = 0;
227    let public_key = PublicKey::from(keypair.public.to_bytes());
228    let farmer_protocol_info = FarmerProtocolInfo {
229        history_size,
230        max_pieces_in_sector: pieces_in_sector,
231        recent_segments: HistorySize::from(NonZeroU64::new(5).unwrap()),
232        recent_history_fraction: (
233            HistorySize::from(NonZeroU64::new(1).unwrap()),
234            HistorySize::from(NonZeroU64::new(10).unwrap()),
235        ),
236        min_sector_lifetime: HistorySize::from(NonZeroU64::new(4).unwrap()),
237    };
238
239    let plotted_sector = plot_sector(PlotSectorOptions {
240        public_key: &public_key,
241        sector_index,
242        piece_getter: &archived_segment,
243        farmer_protocol_info,
244        kzg: &kzg,
245        erasure_coding,
246        pieces_in_sector,
247        sector_output: &mut sector,
248        downloading_semaphore: None,
249        encoding_semaphore: None,
250        records_encoder: &mut CpuRecordsEncoder::<PosTable>::new(
251            slice::from_mut(&mut table_generator),
252            erasure_coding,
253            &Default::default(),
254        ),
255        abort_early: &Default::default(),
256    })
257    .await
258    .expect("Plotting one sector in memory must not fail");
259
260    (sector, plotted_sector, table_generator)
261}