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