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;
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 worker = WorkerState::new(
92 "Single validator node".to_string(),
93 Some(validator_keypair.secret_key.copy()),
94 storage.clone(),
95 5_000,
96 10_000,
97 );
98
99 let key_pair = AccountSecretKey::generate();
101
102 let new_chain_config = InitialChainConfig {
103 ownership: ChainOwnership::single(key_pair.public().into()),
104 min_active_epoch: epoch,
105 max_active_epoch: epoch,
106 epoch,
107 balance: Amount::from_tokens(1_000_000),
108 application_permissions: ApplicationPermissions::default(),
109 };
110
111 let origin = ChainOrigin::Root(0);
112 let description = ChainDescription::new(origin, new_chain_config, Timestamp::from(0));
113 let admin_chain_id = description.id();
114
115 let committee_blob = Blob::new_committee(
116 bcs::to_bytes(&committee).expect("serializing a committee should succeed"),
117 );
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 genesis_committee_blob_hash: committee_blob.id().hash,
124 admin_chain_id,
125 };
126 storage
127 .write_network_description(&network_description)
128 .await
129 .unwrap();
130 storage
131 .write_blob(&committee_blob)
132 .await
133 .expect("writing a blob should succeed");
134 worker
135 .storage_client()
136 .create_chain(description.clone())
137 .await
138 .expect("Failed to create root admin chain");
139
140 let validator = TestValidator {
141 validator_secret: validator_keypair.secret_key,
142 account_secret,
143 committee: Arc::new(Mutex::new((epoch, committee))),
144 storage,
145 worker,
146 clock,
147 admin_chain_id,
148 chains: Arc::default(),
149 };
150
151 let chain = ActiveChain::new(key_pair, description.clone(), validator.clone());
152
153 validator.chains.pin().insert(description.id(), chain);
154
155 validator
156 }
157
158 pub async fn with_current_module<Abi, Parameters, InstantiationArgument>() -> (
163 TestValidator,
164 ModuleId<Abi, Parameters, InstantiationArgument>,
165 ) {
166 let validator = TestValidator::new().await;
167 let publisher = Box::pin(validator.new_chain()).await;
168
169 let module_id = publisher.publish_current_module().await;
170
171 (validator, module_id)
172 }
173
174 pub async fn with_current_application<Abi, Parameters, InstantiationArgument>(
183 parameters: Parameters,
184 instantiation_argument: InstantiationArgument,
185 ) -> (TestValidator, ApplicationId<Abi>, ActiveChain)
186 where
187 Abi: ContractAbi,
188 Parameters: Serialize,
189 InstantiationArgument: Serialize,
190 {
191 let (validator, module_id) =
192 TestValidator::with_current_module::<Abi, Parameters, InstantiationArgument>().await;
193
194 let mut creator = validator.new_chain().await;
195
196 let application_id = creator
197 .create_application(module_id, parameters, instantiation_argument, vec![])
198 .await;
199
200 (validator, application_id, creator)
201 }
202
203 pub(crate) fn storage(&self) -> &DbStorage<MemoryDatabase, TestClock> {
205 &self.storage
206 }
207
208 pub(crate) fn worker(&self) -> WorkerState<DbStorage<MemoryDatabase, TestClock>> {
210 self.worker.clone()
211 }
212
213 pub fn clock(&self) -> &TestClock {
215 &self.clock
216 }
217
218 pub fn key_pair(&self) -> &ValidatorSecretKey {
220 &self.validator_secret
221 }
222
223 pub fn admin_chain_id(&self) -> ChainId {
225 self.admin_chain_id
226 }
227
228 pub async fn committee(&self) -> MappedMutexGuard<'_, (Epoch, Committee), Committee> {
232 MutexGuard::map(self.committee.lock().await, |(_epoch, committee)| committee)
233 }
234
235 pub async fn change_resource_control_policy(
238 &mut self,
239 adjustment: impl FnOnce(&mut ResourceControlPolicy),
240 ) {
241 let (epoch, committee) = {
242 let (ref mut epoch, ref mut committee) = &mut *self.committee.lock().await;
243
244 epoch
245 .try_add_assign_one()
246 .expect("Reached the limit of epochs");
247
248 adjustment(committee.policy_mut());
249
250 (*epoch, committee.clone())
251 };
252
253 let admin_chain = self.get_chain(&self.admin_chain_id);
254
255 let committee_blob = Blob::new(BlobContent::new_committee(
256 bcs::to_bytes(&committee).unwrap(),
257 ));
258 let blob_hash = committee_blob.id().hash;
259 self.storage
260 .write_blob(&committee_blob)
261 .await
262 .expect("Should write committee blob");
263
264 admin_chain
265 .add_block(|block| {
266 block.with_system_operation(SystemOperation::Admin(
267 AdminOperation::CreateCommittee { epoch, blob_hash },
268 ));
269 })
270 .await;
271
272 let pinned = self.chains.pin();
273 for chain in pinned.values() {
274 if chain.id() != self.admin_chain_id {
275 chain
276 .add_block(|block| {
277 block.with_system_operation(SystemOperation::ProcessNewEpoch(epoch));
278 })
279 .await;
280 }
281 }
282 }
283
284 pub async fn new_chain_with_keypair(&self, key_pair: AccountSecretKey) -> ActiveChain {
287 let description = self
288 .request_new_chain_from_admin_chain(key_pair.public().into())
289 .await;
290 let chain = ActiveChain::new(key_pair, description.clone(), self.clone());
291
292 chain.handle_received_messages().await;
293
294 self.chains.pin().insert(description.id(), chain.clone());
295
296 chain
297 }
298
299 pub async fn new_chain(&self) -> ActiveChain {
302 let key_pair = AccountSecretKey::generate();
303 self.new_chain_with_keypair(key_pair).await
304 }
305
306 pub fn add_chain(&self, chain: ActiveChain) {
308 self.chains.pin().insert(chain.id(), chain);
309 }
310
311 async fn request_new_chain_from_admin_chain(&self, owner: AccountOwner) -> ChainDescription {
315 let admin_id = self.admin_chain_id;
316 let pinned = self.chains.pin();
317 let admin_chain = pinned
318 .get(&admin_id)
319 .expect("Admin chain should be created when the `TestValidator` is constructed");
320
321 let (epoch, _) = self.committee.lock().await.clone();
322
323 let open_chain_config = OpenChainConfig {
324 ownership: ChainOwnership::single(owner),
325 balance: Amount::from_tokens(10),
326 application_permissions: ApplicationPermissions::default(),
327 };
328 let new_chain_config = open_chain_config.init_chain_config(epoch, epoch, epoch);
329
330 let certificate = admin_chain
331 .add_block(|block| {
332 block.with_system_operation(SystemOperation::OpenChain(open_chain_config));
333 })
334 .await;
335 let block = certificate.inner().block();
336
337 let origin = ChainOrigin::Child {
338 parent: block.header.chain_id,
339 block_height: block.header.height,
340 chain_index: 0,
341 };
342
343 ChainDescription::new(origin, new_chain_config, Timestamp::from(0))
344 }
345
346 pub fn get_chain(&self, chain_id: &ChainId) -> ActiveChain {
348 self.chains
349 .pin()
350 .get(chain_id)
351 .expect("Chain not found")
352 .clone()
353 }
354}