1use std::{num::NonZeroUsize, sync::Arc};
10
11use dashmap::DashMap;
12use futures::{
13 lock::{MappedMutexGuard, Mutex, MutexGuard},
14 FutureExt as _,
15};
16use linera_base::{
17 crypto::{AccountSecretKey, CryptoHash, ValidatorKeypair, ValidatorSecretKey},
18 data_types::{
19 Amount, ApplicationPermissions, Blob, BlobContent, ChainDescription, ChainOrigin, Epoch,
20 InitialChainConfig, NetworkDescription, Timestamp,
21 },
22 identifiers::{AccountOwner, ApplicationId, ChainId, ModuleId},
23 ownership::ChainOwnership,
24};
25use linera_core::worker::WorkerState;
26use linera_execution::{
27 committee::Committee,
28 system::{AdminOperation, OpenChainConfig, SystemOperation},
29 ResourceControlPolicy, WasmRuntime,
30};
31use linera_storage::{DbStorage, Storage, TestClock};
32use linera_views::memory::MemoryStore;
33use serde::Serialize;
34
35use super::ActiveChain;
36use crate::ContractAbi;
37
38pub struct TestValidator {
52 validator_secret: ValidatorSecretKey,
53 account_secret: AccountSecretKey,
54 committee: Arc<Mutex<(Epoch, Committee)>>,
55 storage: DbStorage<MemoryStore, TestClock>,
56 worker: WorkerState<DbStorage<MemoryStore, TestClock>>,
57 clock: TestClock,
58 admin_chain_id: ChainId,
59 chains: Arc<DashMap<ChainId, ActiveChain>>,
60}
61
62impl Clone for TestValidator {
63 fn clone(&self) -> Self {
64 TestValidator {
65 admin_chain_id: self.admin_chain_id,
66 validator_secret: self.validator_secret.copy(),
67 account_secret: self.account_secret.copy(),
68 committee: self.committee.clone(),
69 storage: self.storage.clone(),
70 worker: self.worker.clone(),
71 clock: self.clock.clone(),
72 chains: self.chains.clone(),
73 }
74 }
75}
76
77impl TestValidator {
78 pub async fn new() -> Self {
80 let validator_keypair = ValidatorKeypair::generate();
81 let account_secret = AccountSecretKey::generate();
82 let epoch = Epoch::ZERO;
83 let committee = Committee::make_simple(vec![(
84 validator_keypair.public_key,
85 account_secret.public(),
86 )]);
87 let wasm_runtime = Some(WasmRuntime::default());
88 let storage = DbStorage::<MemoryStore, _>::make_test_storage(wasm_runtime)
89 .now_or_never()
90 .expect("execution of DbStorage::new should not await anything");
91 let clock = storage.clock().clone();
92 let worker = WorkerState::new(
93 "Single validator node".to_string(),
94 Some(validator_keypair.secret_key.copy()),
95 storage.clone(),
96 NonZeroUsize::new(40).expect("Chain worker limit should not be zero"),
97 );
98
99 let key_pair = AccountSecretKey::generate();
101
102 let new_chain_config = InitialChainConfig {
103 ownership: ChainOwnership::single(key_pair.public().into()),
104 committees: [(
105 epoch,
106 bcs::to_bytes(&committee).expect("Serializing a committee should not fail!"),
107 )]
108 .into_iter()
109 .collect(),
110 epoch,
111 balance: Amount::from_tokens(1_000_000),
112 application_permissions: ApplicationPermissions::default(),
113 };
114
115 let origin = ChainOrigin::Root(0);
116 let description = ChainDescription::new(origin, new_chain_config, Timestamp::from(0));
117 let admin_chain_id = description.id();
118
119 let network_description = NetworkDescription {
120 name: "Test network".to_string(),
121 genesis_config_hash: CryptoHash::test_hash("genesis config"),
122 genesis_timestamp: description.timestamp(),
123 admin_chain_id,
124 };
125 storage
126 .write_network_description(&network_description)
127 .await
128 .unwrap();
129 worker
130 .storage_client()
131 .create_chain(description.clone())
132 .await
133 .expect("Failed to create root admin chain");
134
135 let validator = TestValidator {
136 validator_secret: validator_keypair.secret_key,
137 account_secret,
138 committee: Arc::new(Mutex::new((epoch, committee))),
139 storage,
140 worker,
141 clock,
142 admin_chain_id,
143 chains: Arc::default(),
144 };
145
146 let chain = ActiveChain::new(key_pair, description.clone(), validator.clone());
147
148 validator.chains.insert(description.id(), chain);
149
150 validator
151 }
152
153 pub async fn with_current_module<Abi, Parameters, InstantiationArgument>() -> (
158 TestValidator,
159 ModuleId<Abi, Parameters, InstantiationArgument>,
160 ) {
161 let validator = TestValidator::new().await;
162 let publisher = validator.new_chain().await;
163
164 let module_id = publisher.publish_current_module().await;
165
166 (validator, module_id)
167 }
168
169 pub async fn with_current_application<Abi, Parameters, InstantiationArgument>(
178 parameters: Parameters,
179 instantiation_argument: InstantiationArgument,
180 ) -> (TestValidator, ApplicationId<Abi>, ActiveChain)
181 where
182 Abi: ContractAbi,
183 Parameters: Serialize,
184 InstantiationArgument: Serialize,
185 {
186 let (validator, module_id) =
187 TestValidator::with_current_module::<Abi, Parameters, InstantiationArgument>().await;
188
189 let mut creator = validator.new_chain().await;
190
191 let application_id = creator
192 .create_application(module_id, parameters, instantiation_argument, vec![])
193 .await;
194
195 (validator, application_id, creator)
196 }
197
198 pub(crate) fn storage(&self) -> &DbStorage<MemoryStore, TestClock> {
200 &self.storage
201 }
202
203 pub(crate) fn worker(&self) -> WorkerState<DbStorage<MemoryStore, TestClock>> {
205 self.worker.clone()
206 }
207
208 pub fn clock(&self) -> &TestClock {
210 &self.clock
211 }
212
213 pub fn key_pair(&self) -> &ValidatorSecretKey {
215 &self.validator_secret
216 }
217
218 pub fn admin_chain_id(&self) -> ChainId {
220 self.admin_chain_id
221 }
222
223 pub async fn committee(&self) -> MappedMutexGuard<'_, (Epoch, Committee), Committee> {
227 MutexGuard::map(self.committee.lock().await, |(_epoch, committee)| committee)
228 }
229
230 pub async fn change_resource_control_policy(
233 &mut self,
234 adjustment: impl FnOnce(&mut ResourceControlPolicy),
235 ) {
236 let (epoch, committee) = {
237 let (ref mut epoch, ref mut committee) = &mut *self.committee.lock().await;
238
239 epoch
240 .try_add_assign_one()
241 .expect("Reached the limit of epochs");
242
243 adjustment(committee.policy_mut());
244
245 (*epoch, committee.clone())
246 };
247
248 let admin_chain = self.get_chain(&self.admin_chain_id);
249
250 let committee_blob = Blob::new(BlobContent::new_committee(
251 bcs::to_bytes(&committee).unwrap(),
252 ));
253 let blob_hash = committee_blob.id().hash;
254 self.storage
255 .write_blob(&committee_blob)
256 .await
257 .expect("Should write committee blob");
258
259 admin_chain
260 .add_block(|block| {
261 block.with_system_operation(SystemOperation::Admin(
262 AdminOperation::CreateCommittee { epoch, blob_hash },
263 ));
264 })
265 .await;
266
267 for entry in self.chains.iter() {
268 let chain = entry.value();
269
270 if chain.id() != self.admin_chain_id {
271 chain
272 .add_block(|block| {
273 block.with_system_operation(SystemOperation::ProcessNewEpoch(epoch));
274 })
275 .await;
276 }
277 }
278 }
279
280 pub async fn new_chain_with_keypair(&self, key_pair: AccountSecretKey) -> ActiveChain {
283 let description = self
284 .request_new_chain_from_admin_chain(key_pair.public().into())
285 .await;
286 let chain = ActiveChain::new(key_pair, description.clone(), self.clone());
287
288 chain.handle_received_messages().await;
289
290 self.chains.insert(description.id(), chain.clone());
291
292 chain
293 }
294
295 pub async fn new_chain(&self) -> ActiveChain {
298 let key_pair = AccountSecretKey::generate();
299 self.new_chain_with_keypair(key_pair).await
300 }
301
302 pub fn add_chain(&self, chain: ActiveChain) {
304 self.chains.insert(chain.id(), chain);
305 }
306
307 async fn request_new_chain_from_admin_chain(&self, owner: AccountOwner) -> ChainDescription {
311 let admin_id = self.admin_chain_id;
312 let admin_chain = self
313 .chains
314 .get(&admin_id)
315 .expect("Admin chain should be created when the `TestValidator` is constructed");
316
317 let (epoch, committee) = self.committee.lock().await.clone();
318
319 let open_chain_config = OpenChainConfig {
320 ownership: ChainOwnership::single(owner),
321 balance: Amount::from_tokens(10),
322 application_permissions: ApplicationPermissions::default(),
323 };
324 let new_chain_config = open_chain_config.init_chain_config(
325 epoch,
326 [(
327 epoch,
328 bcs::to_bytes(&committee).expect("Serializing a committee should not fail!"),
329 )]
330 .into_iter()
331 .collect(),
332 );
333
334 let certificate = admin_chain
335 .add_block(|block| {
336 block.with_system_operation(SystemOperation::OpenChain(open_chain_config));
337 })
338 .await;
339 let block = certificate.inner().block();
340
341 let origin = ChainOrigin::Child {
342 parent: block.header.chain_id,
343 block_height: block.header.height,
344 chain_index: 0,
345 };
346
347 ChainDescription::new(origin, new_chain_config, Timestamp::from(0))
348 }
349
350 pub fn get_chain(&self, chain_id: &ChainId) -> ActiveChain {
352 self.chains.get(chain_id).expect("Chain not found").clone()
353 }
354}