1use std::collections::HashSet;
2
3use chia_bls::SecretKey;
4use chia_consensus::validation_error::ErrorCode;
5use chia_protocol::{Bytes32, Coin, CoinSpend, CoinState, Program, SpendBundle};
6use chia_sdk_types::TESTNET11_CONSTANTS;
7use clvmr::ENABLE_KECCAK_OPS_OUTSIDE_GUARD;
8use indexmap::{IndexMap, IndexSet, indexset};
9use rand::{Rng, SeedableRng};
10use rand_chacha::ChaCha8Rng;
11
12use crate::{
13 BlsPair, BlsPairWithCoin, SimulatorError, sign_transaction, validate_clvm_and_signature,
14};
15
16mod config;
17mod data;
18
19pub use config::*;
20
21use data::SimulatorData;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Simulator {
25 config: SimulatorConfig,
26 data: SimulatorData,
27}
28
29impl Default for Simulator {
30 fn default() -> Self {
31 Self::new()
32 }
33}
34
35impl Simulator {
36 pub fn new() -> Self {
37 Self::with_config(SimulatorConfig::default())
38 }
39
40 pub fn with_config(config: SimulatorConfig) -> Self {
41 Self {
42 config,
43 data: SimulatorData::new(ChaCha8Rng::seed_from_u64(config.seed)),
44 }
45 }
46
47 #[cfg(feature = "serde")]
48 pub fn serialize(&self) -> Result<Vec<u8>, bincode::error::EncodeError> {
49 bincode::serde::encode_to_vec(&self.data, bincode::config::standard())
50 }
51
52 #[cfg(feature = "serde")]
53 pub fn deserialize_with_config(
54 data: &[u8],
55 config: SimulatorConfig,
56 ) -> Result<Self, bincode::error::DecodeError> {
57 let data: SimulatorData =
58 bincode::serde::decode_from_slice(data, bincode::config::standard())?.0;
59 Ok(Self { config, data })
60 }
61
62 #[cfg(feature = "serde")]
63 pub fn deserialize(data: &[u8]) -> Result<Self, bincode::error::DecodeError> {
64 Self::deserialize_with_config(data, SimulatorConfig::default())
65 }
66
67 pub fn height(&self) -> u32 {
68 self.data.height
69 }
70
71 pub fn next_timestamp(&self) -> u64 {
72 self.data.next_timestamp
73 }
74
75 pub fn header_hash(&self) -> Bytes32 {
76 self.data.header_hashes.last().copied().unwrap()
77 }
78
79 pub fn header_hash_of(&self, height: u32) -> Option<Bytes32> {
80 self.data.header_hashes.get(height as usize).copied()
81 }
82
83 pub fn insert_coin(&mut self, coin: Coin) {
84 let coin_state = CoinState::new(coin, None, Some(self.data.height));
85 self.data.coin_states.insert(coin.coin_id(), coin_state);
86 }
87
88 pub fn new_coin(&mut self, puzzle_hash: Bytes32, amount: u64) -> Coin {
89 let mut parent_coin_info = [0; 32];
90 self.data.rng.fill(&mut parent_coin_info);
91 let coin = Coin::new(parent_coin_info.into(), puzzle_hash, amount);
92 self.insert_coin(coin);
93 coin
94 }
95
96 pub fn bls(&mut self, amount: u64) -> BlsPairWithCoin {
97 let pair = BlsPair::new(self.data.rng.random());
98 let coin = self.new_coin(pair.puzzle_hash, amount);
99 BlsPairWithCoin::new(pair, coin)
100 }
101
102 pub fn set_next_timestamp(&mut self, time: u64) -> Result<(), SimulatorError> {
103 if self.data.height > 0
104 && let Some(last_block_timestamp) =
105 self.data.block_timestamps.get(&(self.data.height - 1))
106 && time < *last_block_timestamp
107 {
108 return Err(SimulatorError::Validation(ErrorCode::TimestampTooFarInPast));
109 }
110 self.data.next_timestamp = time;
111
112 Ok(())
113 }
114
115 pub fn pass_time(&mut self, time: u64) {
116 self.data.next_timestamp += time;
117 }
118
119 pub fn hint_coin(&mut self, coin_id: Bytes32, hint: Bytes32) {
120 self.data
121 .hinted_coins
122 .entry(hint)
123 .or_default()
124 .insert(coin_id);
125 }
126
127 pub fn coin_state(&self, coin_id: Bytes32) -> Option<CoinState> {
128 self.data.coin_states.get(&coin_id).copied()
129 }
130
131 pub fn children(&self, coin_id: Bytes32) -> Vec<CoinState> {
132 self.data
133 .coin_states
134 .values()
135 .filter(move |cs| cs.coin.parent_coin_info == coin_id)
136 .copied()
137 .collect()
138 }
139
140 pub fn hinted_coins(&self, hint: Bytes32) -> Vec<Bytes32> {
141 self.data
142 .hinted_coins
143 .get(&hint)
144 .into_iter()
145 .flatten()
146 .copied()
147 .collect()
148 }
149
150 pub fn puzzle_reveal(&self, coin_id: Bytes32) -> Option<Program> {
151 self.data
152 .coin_spends
153 .get(&coin_id)
154 .map(|spend| spend.puzzle_reveal.clone())
155 }
156
157 pub fn solution(&self, coin_id: Bytes32) -> Option<Program> {
158 self.data
159 .coin_spends
160 .get(&coin_id)
161 .map(|spend| spend.solution.clone())
162 }
163
164 pub fn puzzle_and_solution(&self, coin_id: Bytes32) -> Option<(Program, Program)> {
165 self.data
166 .coin_spends
167 .get(&coin_id)
168 .map(|spend| (spend.puzzle_reveal.clone(), spend.solution.clone()))
169 }
170
171 pub fn coin_spend(&self, coin_id: Bytes32) -> Option<CoinSpend> {
172 self.data.coin_spends.get(&coin_id).cloned()
173 }
174
175 pub fn spend_coins(
176 &mut self,
177 coin_spends: Vec<CoinSpend>,
178 secret_keys: &[SecretKey],
179 ) -> Result<IndexMap<Bytes32, CoinState>, SimulatorError> {
180 let signature = sign_transaction(&coin_spends, secret_keys)?;
181 self.new_transaction(SpendBundle::new(coin_spends, signature))
182 }
183
184 pub fn new_transaction(
186 &mut self,
187 spend_bundle: SpendBundle,
188 ) -> Result<IndexMap<Bytes32, CoinState>, SimulatorError> {
189 if spend_bundle.coin_spends.is_empty() {
190 return Err(SimulatorError::Validation(ErrorCode::InvalidSpendBundle));
191 }
192
193 let conds = validate_clvm_and_signature(
194 &spend_bundle,
195 11_000_000_000 / 2,
196 &TESTNET11_CONSTANTS,
197 ENABLE_KECCAK_OPS_OUTSIDE_GUARD,
198 )
199 .map_err(SimulatorError::Validation)?;
200
201 let puzzle_hashes: HashSet<Bytes32> =
202 conds.spends.iter().map(|spend| spend.puzzle_hash).collect();
203
204 let bundle_puzzle_hashes: HashSet<Bytes32> = spend_bundle
205 .coin_spends
206 .iter()
207 .map(|cs| cs.coin.puzzle_hash)
208 .collect();
209
210 if puzzle_hashes != bundle_puzzle_hashes {
211 return Err(SimulatorError::Validation(ErrorCode::InvalidSpendBundle));
212 }
213
214 let mut removed_coins = IndexMap::new();
215 let mut added_coins = IndexMap::new();
216 let mut added_hints = IndexMap::new();
217 let mut coin_spends = IndexMap::new();
218
219 if self.data.height < conds.height_absolute {
220 return Err(SimulatorError::Validation(
221 ErrorCode::AssertHeightAbsoluteFailed,
222 ));
223 }
224
225 if self.data.next_timestamp < conds.seconds_absolute {
226 return Err(SimulatorError::Validation(
227 ErrorCode::AssertSecondsAbsoluteFailed,
228 ));
229 }
230
231 if let Some(height) = conds.before_height_absolute
232 && height < self.data.height
233 {
234 return Err(SimulatorError::Validation(
235 ErrorCode::AssertBeforeHeightAbsoluteFailed,
236 ));
237 }
238
239 if let Some(seconds) = conds.before_seconds_absolute
240 && seconds < self.data.next_timestamp
241 {
242 return Err(SimulatorError::Validation(
243 ErrorCode::AssertBeforeSecondsAbsoluteFailed,
244 ));
245 }
246
247 for coin_spend in spend_bundle.coin_spends {
248 coin_spends.insert(coin_spend.coin.coin_id(), coin_spend);
249 }
250
251 for spend in &conds.spends {
253 for new_coin in &spend.create_coin {
254 let coin = Coin::new(spend.coin_id, new_coin.0, new_coin.1);
255
256 added_coins.insert(
257 coin.coin_id(),
258 CoinState::new(coin, None, Some(self.data.height)),
259 );
260
261 let Some(hint) = new_coin.2.clone() else {
262 continue;
263 };
264
265 if hint.len() != 32 {
266 continue;
267 }
268
269 added_hints
270 .entry(Bytes32::try_from(hint).unwrap())
271 .or_insert_with(IndexSet::new)
272 .insert(coin.coin_id());
273 }
274
275 let coin = Coin::new(spend.parent_id, spend.puzzle_hash, spend.coin_amount);
276
277 let coin_state = self
278 .data
279 .coin_states
280 .get(&spend.coin_id)
281 .copied()
282 .unwrap_or(CoinState::new(coin, None, Some(self.data.height)));
283
284 if let Some(relative_height) = spend.height_relative {
285 let Some(created_height) = coin_state.created_height else {
286 return Err(SimulatorError::Validation(
287 ErrorCode::EphemeralRelativeCondition,
288 ));
289 };
290
291 if self.data.height < created_height + relative_height {
292 return Err(SimulatorError::Validation(
293 ErrorCode::AssertHeightRelativeFailed,
294 ));
295 }
296 }
297
298 if let Some(relative_seconds) = spend.seconds_relative {
299 let Some(created_height) = coin_state.created_height else {
300 return Err(SimulatorError::Validation(
301 ErrorCode::EphemeralRelativeCondition,
302 ));
303 };
304 let Some(created_timestamp) = self.data.block_timestamps.get(&created_height)
305 else {
306 return Err(SimulatorError::Validation(
307 ErrorCode::EphemeralRelativeCondition,
308 ));
309 };
310
311 if self.data.next_timestamp < created_timestamp + relative_seconds {
312 return Err(SimulatorError::Validation(
313 ErrorCode::AssertSecondsRelativeFailed,
314 ));
315 }
316 }
317
318 if let Some(relative_height) = spend.before_height_relative {
319 let Some(created_height) = coin_state.created_height else {
320 return Err(SimulatorError::Validation(
321 ErrorCode::EphemeralRelativeCondition,
322 ));
323 };
324
325 if created_height + relative_height < self.data.height {
326 return Err(SimulatorError::Validation(
327 ErrorCode::AssertBeforeHeightRelativeFailed,
328 ));
329 }
330 }
331
332 if let Some(relative_seconds) = spend.before_seconds_relative {
333 let Some(created_height) = coin_state.created_height else {
334 return Err(SimulatorError::Validation(
335 ErrorCode::EphemeralRelativeCondition,
336 ));
337 };
338 let Some(created_timestamp) = self.data.block_timestamps.get(&created_height)
339 else {
340 return Err(SimulatorError::Validation(
341 ErrorCode::EphemeralRelativeCondition,
342 ));
343 };
344
345 if created_timestamp + relative_seconds < self.data.next_timestamp {
346 return Err(SimulatorError::Validation(
347 ErrorCode::AssertBeforeSecondsRelativeFailed,
348 ));
349 }
350 }
351
352 removed_coins.insert(spend.coin_id, coin_state);
353 }
354
355 for (coin_id, coin_state) in &mut removed_coins {
357 let height = self.data.height;
358
359 if !self.data.coin_states.contains_key(coin_id) && !added_coins.contains_key(coin_id) {
360 return Err(SimulatorError::Validation(ErrorCode::UnknownUnspent));
361 }
362
363 if coin_state.spent_height.is_some() {
364 return Err(SimulatorError::Validation(ErrorCode::DoubleSpend));
365 }
366
367 coin_state.spent_height = Some(height);
368 }
369
370 let mut updates = added_coins.clone();
372 updates.extend(removed_coins);
373
374 self.create_block();
375
376 self.data.coin_states.extend(updates.clone());
377
378 if self.config.save_hints {
379 for (hint, coins) in added_hints {
380 self.data
381 .hinted_coins
382 .entry(hint)
383 .or_default()
384 .extend(coins);
385 }
386 }
387
388 if self.config.save_spends {
389 self.data.coin_spends.extend(coin_spends);
390 }
391
392 Ok(updates)
393 }
394
395 pub fn lookup_coin_ids(&self, coin_ids: &IndexSet<Bytes32>) -> Vec<CoinState> {
396 coin_ids
397 .iter()
398 .filter_map(|coin_id| self.data.coin_states.get(coin_id).copied())
399 .collect()
400 }
401
402 pub fn lookup_puzzle_hashes(
403 &self,
404 puzzle_hashes: IndexSet<Bytes32>,
405 include_hints: bool,
406 ) -> Vec<CoinState> {
407 let mut coin_states = IndexMap::new();
408
409 for (coin_id, coin_state) in &self.data.coin_states {
410 if puzzle_hashes.contains(&coin_state.coin.puzzle_hash) {
411 coin_states.insert(*coin_id, self.data.coin_states[coin_id]);
412 }
413 }
414
415 if include_hints {
416 for puzzle_hash in puzzle_hashes {
417 if let Some(hinted_coins) = self.data.hinted_coins.get(&puzzle_hash) {
418 for coin_id in hinted_coins {
419 coin_states.insert(*coin_id, self.data.coin_states[coin_id]);
420 }
421 }
422 }
423 }
424
425 coin_states.into_values().collect()
426 }
427
428 pub fn unspent_coins(&self, puzzle_hash: Bytes32, include_hints: bool) -> Vec<Coin> {
429 self.lookup_puzzle_hashes(indexset![puzzle_hash], include_hints)
430 .iter()
431 .filter(|cs| cs.spent_height.is_none())
432 .map(|cs| cs.coin)
433 .collect()
434 }
435
436 pub fn create_block(&mut self) {
437 let mut header_hash = [0; 32];
438 self.data.rng.fill(&mut header_hash);
439 self.data.header_hashes.push(header_hash.into());
440 self.data
441 .block_timestamps
442 .insert(self.data.height, self.data.next_timestamp);
443
444 self.data.height += 1;
445 self.data.next_timestamp += 1;
446 }
447}