oasis_runtime_sdk/testing/
mock.rs1use std::collections::BTreeMap;
3
4use oasis_core_runtime::{
5 common::{crypto::mrae::deoxysii, namespace::Namespace, version::Version},
6 consensus::{beacon, roothash, state::ConsensusState, Event},
7 protocol::HostInfo,
8 storage::mkvs,
9 types::EventKind,
10};
11
12use crate::{
13 callformat,
14 context::{Context, RuntimeBatchContext},
15 dispatcher,
16 error::RuntimeError,
17 history,
18 keymanager::KeyManager,
19 module::MigrationHandler,
20 modules,
21 runtime::Runtime,
22 state::{self, CurrentState, TransactionResult},
23 storage::MKVSStore,
24 testing::{configmap, keymanager::MockKeyManagerClient},
25 types::{self, address::SignatureAddressSpec, transaction},
26};
27
28pub struct Config;
29
30impl modules::core::Config for Config {}
31
32pub struct EmptyRuntime;
34
35impl Runtime for EmptyRuntime {
36 const VERSION: Version = Version::new(0, 0, 0);
37
38 type Core = modules::core::Module<Config>;
39
40 type Accounts = modules::accounts::Module;
41
42 type Modules = modules::core::Module<Config>;
43
44 fn genesis_state() -> <Self::Modules as MigrationHandler>::Genesis {
45 Default::default()
46 }
47}
48
49struct EmptyHistory;
50
51impl history::HistoryHost for EmptyHistory {
52 fn consensus_state_at(&self, _height: u64) -> Result<ConsensusState, history::Error> {
53 Err(history::Error::FailedToFetchBlock)
54 }
55
56 fn consensus_events_at(
57 &self,
58 _height: u64,
59 _kind: EventKind,
60 ) -> Result<Vec<Event>, history::Error> {
61 Err(history::Error::FailedToFetchEvents)
62 }
63}
64
65pub struct Mock {
67 pub host_info: HostInfo,
68 pub runtime_header: roothash::Header,
69 pub runtime_round_results: roothash::RoundResults,
70 pub consensus_state: ConsensusState,
71 pub history: Box<dyn history::HistoryHost>,
72 pub epoch: beacon::EpochTime,
73
74 pub max_messages: u32,
75}
76
77impl Mock {
78 pub fn create_ctx(&mut self) -> RuntimeBatchContext<'_, EmptyRuntime> {
80 self.create_ctx_for_runtime(false)
81 }
82
83 pub fn create_ctx_for_runtime<R: Runtime>(
85 &mut self,
86 confidential: bool,
87 ) -> RuntimeBatchContext<'_, R> {
88 RuntimeBatchContext::new(
89 &self.host_info,
90 if confidential {
91 Some(Box::new(MockKeyManagerClient::new()) as Box<dyn KeyManager>)
92 } else {
93 None
94 },
95 &self.runtime_header,
96 &self.runtime_round_results,
97 &self.consensus_state,
98 &self.history,
99 self.epoch,
100 self.max_messages,
101 )
102 }
103
104 pub fn with_local_config(local_config: BTreeMap<String, cbor::Value>) -> Self {
106 CurrentState::init_local_fallback();
109
110 let consensus_tree = mkvs::Tree::builder()
111 .with_root_type(mkvs::RootType::State)
112 .build(Box::new(mkvs::sync::NoopReadSyncer));
113
114 Self {
115 host_info: HostInfo {
116 runtime_id: Namespace::default(),
117 consensus_backend: "mock".to_string(),
118 consensus_protocol_version: Version::default(),
119 consensus_chain_context: "test".to_string(),
120 local_config,
121 },
122 runtime_header: roothash::Header::default(),
123 runtime_round_results: roothash::RoundResults::default(),
124 consensus_state: ConsensusState::new(1, consensus_tree),
125 history: Box::new(EmptyHistory),
126 epoch: 1,
127 max_messages: 32,
128 }
129 }
130}
131
132impl Default for Mock {
133 fn default() -> Self {
134 let local_config_for_tests = configmap! {
135 "estimate_gas_by_simulating_contracts" => true,
137 "allowed_queries" => vec![
138 configmap! {"all_expensive" => true}
139 ],
140 };
141 Self::with_local_config(local_config_for_tests)
142 }
143}
144
145pub fn empty_store() -> MKVSStore<mkvs::OverlayTree<mkvs::Tree>> {
147 let root = mkvs::OverlayTree::new(
148 mkvs::Tree::builder()
149 .with_root_type(mkvs::RootType::State)
150 .build(Box::new(mkvs::sync::NoopReadSyncer)),
151 );
152 MKVSStore::new(root)
153}
154
155pub fn transaction() -> transaction::Transaction {
157 transaction::Transaction {
158 version: 1,
159 call: transaction::Call {
160 format: transaction::CallFormat::Plain,
161 method: "mock".to_owned(),
162 body: cbor::Value::Simple(cbor::SimpleValue::NullValue),
163 ..Default::default()
164 },
165 auth_info: transaction::AuthInfo {
166 signer_info: vec![],
167 fee: transaction::Fee {
168 amount: Default::default(),
169 gas: 1_000_000,
170 consensus_messages: 32,
171 ..Default::default()
172 },
173 ..Default::default()
174 },
175 }
176}
177
178#[derive(Clone, Debug)]
180pub struct CallOptions {
181 pub fee: transaction::Fee,
183 pub encrypted: bool,
185}
186
187impl Default for CallOptions {
188 fn default() -> Self {
189 Self {
190 fee: transaction::Fee {
191 amount: Default::default(),
192 gas: 1_000_000,
193 consensus_messages: 0,
194 ..Default::default()
195 },
196 encrypted: false,
197 }
198 }
199}
200
201pub struct Signer {
203 nonce: u64,
204 sigspec: SignatureAddressSpec,
205}
206
207impl Signer {
208 pub fn new(nonce: u64, sigspec: SignatureAddressSpec) -> Self {
210 Self { nonce, sigspec }
211 }
212
213 pub fn sigspec(&self) -> &SignatureAddressSpec {
215 &self.sigspec
216 }
217
218 pub fn call<C, B>(&mut self, ctx: &C, method: &str, body: B) -> dispatcher::DispatchResult
220 where
221 C: Context,
222 B: cbor::Encode,
223 {
224 self.call_opts(ctx, method, body, Default::default())
225 }
226
227 pub fn call_opts<C, B>(
229 &mut self,
230 ctx: &C,
231 method: &str,
232 body: B,
233 opts: CallOptions,
234 ) -> dispatcher::DispatchResult
235 where
236 C: Context,
237 B: cbor::Encode,
238 {
239 let mut call = transaction::Call {
240 format: transaction::CallFormat::Plain,
241 method: method.to_owned(),
242 body: cbor::to_value(body),
243 ..Default::default()
244 };
245 if opts.encrypted {
246 let key_pair = deoxysii::generate_key_pair();
247 let nonce = [0u8; deoxysii::NONCE_SIZE];
248 let km = ctx.key_manager().unwrap();
249 let epoch = ctx.epoch();
250 let runtime_keypair = km
251 .get_or_create_ephemeral_keys(callformat::get_key_pair_id(epoch), epoch)
252 .unwrap();
253 let runtime_pk = runtime_keypair.input_keypair.pk;
254 call = transaction::Call {
255 format: transaction::CallFormat::EncryptedX25519DeoxysII,
256 method: "".to_owned(),
257 body: cbor::to_value(types::callformat::CallEnvelopeX25519DeoxysII {
258 pk: key_pair.0.into(),
259 nonce,
260 epoch,
261 data: deoxysii::box_seal(
262 &nonce,
263 cbor::to_vec(call),
264 vec![],
265 &runtime_pk.0,
266 &key_pair.1,
267 )
268 .unwrap(),
269 }),
270 ..Default::default()
271 }
272 };
273 let tx = transaction::Transaction {
274 version: 1,
275 call,
276 auth_info: transaction::AuthInfo {
277 signer_info: vec![transaction::SignerInfo::new_sigspec(
278 self.sigspec.clone(),
279 self.nonce,
280 )],
281 fee: opts.fee,
282 ..Default::default()
283 },
284 };
285
286 let result = dispatcher::Dispatcher::<C::Runtime>::dispatch_tx(ctx, 1024, tx, 0)
287 .expect("dispatch should work");
288
289 self.nonce += 1;
291
292 result
293 }
294
295 pub fn query<C, A, R>(&self, ctx: &C, method: &str, args: A) -> Result<R, RuntimeError>
297 where
298 C: Context,
299 A: cbor::Encode,
300 R: cbor::Decode,
301 {
302 let result = CurrentState::with_transaction_opts(
303 state::Options::new().with_mode(state::Mode::Check),
304 || {
305 let result = dispatcher::Dispatcher::<C::Runtime>::dispatch_query(
306 ctx,
307 method,
308 cbor::to_vec(args),
309 );
310
311 TransactionResult::Rollback(result)
312 },
313 )?;
314 Ok(cbor::from_slice(&result).expect("result should decode correctly"))
315 }
316}