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