Coverage Report

Created: 2026-09-11 07:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/runner/work/lyquor/lyquor/platform/hosting/src/lvm.rs
Line
Count
Source
1
use std::sync::Arc;
2
use std::time::{Duration, SystemTime, UNIX_EPOCH};
3
4
use crate::egress::{Egress, EgressConfig};
5
use async_trait::async_trait;
6
use lyquor_api::{
7
    anyhow,
8
    call::CallParams,
9
    interface::{
10
        FetchOracleInfo, GetAddressByEd25519, GetAddressByEd25519Service, GetEd25519ByAddress,
11
        GetEd25519ByAddressService, GetEthContractAddr, GetEthContractAddrService, InterCallService, OnInterCall,
12
        OracleInfoService, SubmitCall, SubmitService,
13
    },
14
    store::{KVStore, KVStoreError, StoreFuture},
15
};
16
use lyquor_crypto as crypto;
17
use lyquor_primitives::oracle::OracleTarget;
18
use lyquor_primitives::{
19
    Address, Bytes, ConsoleSink, LyquidID, LyquidNumber, NodeID, SequenceBackendID, encode_object,
20
};
21
use lyquor_upc::{CallHeader, MulticastContext, RemoteObj};
22
use lyquor_vm::{RuntimeEnv, lyquid};
23
use thiserror::Error;
24
use tokio::sync::mpsc;
25
use tower::{ServiceExt, util::BoxCloneSyncService};
26
27
/// Per-Lyquid stores required to construct a VM instance.
28
pub struct ProcessStores {
29
    pub vm: lyquor_vm::InstanceStores,
30
    pub progress: Arc<dyn KVStore>,
31
}
32
33
/// Factory that returns namespaced stores for a Lyquid process.
34
pub type ProcessStoreFactory = Arc<dyn Fn(LyquidID) -> StoreFuture<Result<ProcessStores, KVStoreError>> + Send + Sync>;
35
36
/// Sequencing services exposed to hosted Lyquid VM host APIs.
37
#[derive(Clone)]
38
pub struct Sequencer {
39
    pub inter: InterCallService,
40
    pub submit: SubmitService,
41
    pub fetch_oracle_info: OracleInfoService,
42
}
43
44
/// Bartender lookup services exposed to hosted Lyquid VM host APIs.
45
#[derive(Clone, Debug)]
46
pub struct Bartender {
47
    pub get_address_by_ed25519: GetAddressByEd25519Service,
48
    pub get_ed25519_by_address: GetEd25519ByAddressService,
49
    pub get_eth_contract_address: GetEthContractAddrService,
50
}
51
52
/// Console output emitted by a hosted Lyquid.
53
#[derive(Debug, Clone)]
54
pub struct ConsoleOutput {
55
    pub sink: ConsoleSink,
56
    pub data: String,
57
}
58
59
/// Trigger registration or removal requested by a hosted Lyquid (see [`lyquor_primitives::TriggerMode`]).
60
#[derive(Debug, Clone)]
61
pub struct Trigger {
62
    pub group: String,
63
    pub method: String,
64
    pub input: Vec<u8>,
65
    pub version: LyquidNumber,
66
    pub mode: lyquor_primitives::TriggerMode,
67
}
68
69
/// Service used by VM host APIs to register or stop triggers.
70
pub type TriggerService = BoxCloneSyncService<Trigger, (), anyhow::Error>;
71
/// Shared VM instance type for hosting runtime environments.
72
pub type Instance = lyquor_vm::Instance<Env>;
73
74
#[derive(Clone)]
75
struct NodeEnv {
76
    upc: lyquor_upc::Requester<lyquor_vm::instance::Error>,
77
    node_id: NodeID,
78
    sequencer: Option<Sequencer>,
79
    sig_provider: Arc<crypto::SigProvider>,
80
    bartender: Option<Bartender>,
81
    // Chain-level constant available to every instance (including bartender,
82
    // which runs without the registry-backed `Bartender` service bundle).
83
    sequence_backend_id: SequenceBackendID,
84
}
85
86
#[derive(Clone)]
87
struct LyquidEnv {
88
    lyquid_id: LyquidID,
89
    console: Option<mpsc::Sender<ConsoleOutput>>,
90
    egress: Egress,
91
    trigger: Option<TriggerService>,
92
}
93
94
/// Runtime environment captured by hosted Lyquid VM instances.
95
#[derive(Clone)]
96
pub struct Env {
97
    node: NodeEnv,
98
    lyquid: LyquidEnv,
99
}
100
101
impl Env {
102
2
    fn sequencer(&self) -> Option<&Sequencer> {
103
2
        self.node.sequencer.as_ref()
104
2
    }
105
}
106
107
impl lyquor_vm::RuntimeEnv for Env {
108
472
    fn get_lyquid_id(&self) -> LyquidID {
109
472
        self.lyquid.lyquid_id
110
472
    }
111
112
384
    fn get_node_id(&self) -> NodeID {
113
384
        self.node.node_id
114
384
    }
115
}
116
117
/// Errors returned while constructing or wiring hosted VM runtime state.
118
#[derive(Debug, Error)]
119
pub enum VmRuntimeError {
120
    #[error("VM: {0}")]
121
    VM(#[from] lyquor_vm::Error),
122
    #[error("UPC: {0}")]
123
    UPC(#[from] lyquor_upc::Error<lyquor_vm::instance::Error>),
124
}
125
126
type HostAPIFunc = lyquor_vm::barrel::HostAPIFunc<lyquor_vm::instance::Host<Env>>;
127
128
lazy_static::lazy_static! {
129
158
    static ref LVM_CONSOLE: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>; sink: ConsoleSink, data: String) {
130
        // Silently drop the message if the output is not available.
131
158
        if let Some(
console0
) = &host.env.lyquid.console {
132
0
            console.send(ConsoleOutput {
133
0
                sink,
134
0
                data,
135
0
            }).await.ok();
136
158
        }
137
158
        Ok(())
138
    });
139
140
    static ref LVM_UPC: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>;
141
7
            target: LyquidID, group: Option<String>, method: String, input: Vec<u8>, client_params: Option<Bytes>, timeout_ms: Option<u64>) -> Vec<u8> {
142
7
        if host.category != lyquor_primitives::StateCategory::Instance {
143
1
            return Err(lyquid::LyquidError::LyquorRuntime(
144
1
                "`universal_procedural_call` only allowed in instance functions".to_string(),
145
1
            ));
146
6
        }
147
6
        let timeout = timeout_ms.map_or_else(
148
6
            || tokio::time::Duration::from_secs(lyquor_upc::message::UPC_PROCEDURE_TIMEOUT_DEFAULT),
149
            Duration::from_millis,
150
        );
151
152
6
        tracing::trace!(
153
            lyquid_id = %host.env.lyquid.lyquid_id,
154
            target_lyquid_id = %target,
155
            group = ?group,
156
            method = %method,
157
0
            input_bytes = input.len(),
158
0
            client_params_bytes = client_params.as_ref().map_or(0, Bytes::len),
159
0
            timeout_ms = timeout.as_millis() as u64,
160
            "host handling UPC call"
161
        );
162
163
6
        let number = if target == host.env.lyquid.lyquid_id {
164
3
            Some(*host.version.read().await)
165
        } else {
166
3
            None
167
        };
168
6
        host.env.node.upc.call(CallHeader {
169
6
            lyquid: target,
170
6
            method,
171
6
            number,
172
6
            group,
173
6
            timeout,
174
6
            context: None,
175
6
        }, RemoteObj::from_encoded(input.into()), client_params, None).await
176
6
            .map_err(|e| lyquid::LyquidError::LyquorRuntime(
format!0
("UPC failed: {e}")))
177
6
            .map(|r| Vec::from(r.to_encoded()))
178
    });
179
180
    static ref LVM_INTER_LYQUID_CALL: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>;
181
2
            callee: LyquidID, method: String, input: Vec<u8>) -> Vec<u8> {
182
2
        if host.category != lyquor_primitives::StateCategory::Network {
183
0
            return Err(lyquid::LyquidError::LyquorRuntime(
184
0
                "`inter_lyquid_call` only allowed in network functions".to_string(),
185
0
            ));
186
2
        }
187
188
2
        let pos = *host.chain_pos.read().await;
189
2
        if let Some(sys) = host.env.sequencer() {
190
2
            let caller = host.env.lyquid.lyquid_id;
191
2
            sys.inter
192
2
                .clone()
193
2
                .oneshot(OnInterCall {
194
2
                    origin: caller.into(), // TODO: pass on the real origin
195
2
                    caller,
196
2
                    callee,
197
2
                    method,
198
2
                    input,
199
2
                    pos,
200
2
                })
201
2
                .await
202
2
                .map_err(|e| lyquid::LyquidError::LyquorRuntime(
format!0
("error during service call: {e:?}")))
203
        } else {
204
0
            Err(lyquid::LyquidError::LyquorRuntime("InterCallSys not available".into()))
205
        }
206
    });
207
208
0
    static ref LVM_SUBMIT_CALL: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>; call: CallParams, signed: bool) -> Vec<u8> {
209
0
        if host.category != lyquor_primitives::StateCategory::Instance {
210
0
            return Err(lyquid::LyquidError::LyquorRuntime(
211
0
                "`submit_call` only allowed in instance functions".to_string(),
212
0
            ));
213
0
        }
214
215
0
        let lyquid = host.env.lyquid.lyquid_id;
216
0
        if let Some(sys) = host.env.sequencer() {
217
0
            sys.submit.clone().oneshot(SubmitCall {
218
0
                lyquid,
219
0
                params: call,
220
0
                signed,
221
0
            }).await.map_err(|e|
222
0
                lyquid::LyquidError::LyquorRuntime(format!("submit call error: {e:?}"))
223
            )
224
        } else {
225
0
            Err(lyquid::LyquidError::LyquorRuntime("submit call recipient unavailable".into()))
226
        }
227
    });
228
229
    static ref LVM_SIGN: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>;
230
            msg: Bytes,
231
0
            cipher: lyquor_primitives::Cipher) -> lyquor_primitives::Signature {
232
0
        if host.category != lyquor_primitives::StateCategory::Instance {
233
0
            return Err(lyquid::LyquidError::LyquorRuntime(
234
0
                "`sign` only allowed in instance functions".to_string(),
235
0
            ));
236
0
        }
237
238
0
        let sig = host.env.node.sig_provider.sign(cipher, msg).await.unwrap_or_else(Bytes::new);
239
0
        Ok(sig)
240
    });
241
242
    static ref LVM_VERIFY: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>;
243
            msg: Bytes,
244
            cipher: lyquor_primitives::Cipher,
245
            sig: Bytes,
246
0
            pubkey: Bytes) -> bool {
247
0
        Ok(host.env.node.sig_provider.verify(cipher, msg, sig, pubkey).await)
248
    });
249
250
0
    static ref LVM_RNG: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>; length: usize) -> Vec<u8> {
251
0
        if host.category != lyquor_primitives::StateCategory::Instance {
252
0
            return Err(lyquid::LyquidError::LyquorRuntime(
253
0
                "random_bytes only allowed in instance functions (for now)".to_string(),
254
0
            ));
255
0
        }
256
257
0
        let len = if length > 256 {
258
0
            256
259
        } else {
260
0
            length
261
        };
262
        use rand::Rng;
263
0
        let mut bytes = vec![0u8; len];
264
0
        rand::rng().fill_bytes(&mut bytes);
265
0
        Ok(bytes)
266
    });
267
268
0
    static ref LVM_GET_TIME: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>;) -> u64 {
269
0
        if host.category != lyquor_primitives::StateCategory::Instance {
270
0
            return Err(lyquid::LyquidError::LyquorRuntime(
271
0
                "`systime` only allowed in instance functions".to_string(),
272
0
            ));
273
0
        }
274
275
0
        let now = SystemTime::now()
276
0
            .duration_since(UNIX_EPOCH)
277
0
            .map_err(|e| lyquid::LyquidError::LyquorRuntime(format!("System time before unix epoch: {e}.")))?;
278
0
        u64::try_from(now.as_millis())
279
0
            .map_err(|_| lyquid::LyquidError::LyquorRuntime("Timestamp overflow.".to_string()))
280
    });
281
282
    static ref LVM_HTTP_REQUEST: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>;
283
            request: lyquid::http::Request,
284
9
            options: Option<lyquid::http::RequestOptions>) -> lyquid::http::Response {
285
9
        if host.category != lyquor_primitives::StateCategory::Instance {
286
0
            return Err(lyquid::LyquidError::LyquorRuntime(
287
0
                "`http_request` only allowed in instance functions".to_string(),
288
0
            ));
289
9
        }
290
291
9
        host.env.lyquid.egress.clone().send(request, options).await
292
    });
293
294
0
    static ref LVM_GET_ED25519_QXY: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>; pubkey: [u8; 32]) -> (lyquor_primitives::U256, lyquor_primitives::U256) {
295
0
        let ret = crypto::ed25519::SCLPubkey::new(pubkey);
296
0
        Ok((ret.qx, ret.qy))
297
    });
298
299
0
    static ref LVM_GET_ADDRESS_BY_ED25519: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>; pubkey: [u8; 32]) -> Option<Address> {
300
        // NOTE: The result of this function is deterministic for network function, because we use the
301
        // same chain position here for the address retrieval.
302
0
        let pos = *host.chain_pos.read().await;
303
0
        host.env.node.bartender
304
0
            .as_ref()
305
0
            .ok_or_else(|| lyquid::LyquidError::LyquorRuntime("GetAddressByEd25519 function not supported.".into()))?
306
0
            .get_address_by_ed25519.clone()
307
0
            .oneshot(GetAddressByEd25519 {
308
0
                pos,
309
0
                id: pubkey.into(),
310
0
            })
311
0
            .await
312
0
            .map_err(|e| lyquid::LyquidError::LyquorRuntime(format!("GetAddressByEd25519 failed: {e}")))
313
    });
314
315
0
    static ref LVM_GET_ED25519_BY_ADDRESS: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>; address: Address) -> Option<NodeID> {
316
0
        let pos = *host.chain_pos.read().await;
317
0
        host.env.node.bartender
318
0
            .as_ref()
319
0
            .ok_or_else(|| lyquid::LyquidError::LyquorRuntime("GetEd25519ByAddress function not supported.".into()))?
320
0
            .get_ed25519_by_address.clone()
321
0
            .oneshot(GetEd25519ByAddress {
322
0
                pos,
323
0
                address,
324
0
            })
325
0
            .await
326
0
            .map_err(|e| lyquid::LyquidError::LyquorRuntime(format!("GetEd25519ByAddress failed: {e}")))
327
    });
328
329
12
    static ref LVM_ETH_CONTRACT: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>;) -> Option<Address> {
330
        // NOTE: The result of this function is deterministic for network function, because we use the
331
        // same chain position here for the address retrieval.
332
12
        let pos = *host.chain_pos.read().await;
333
12
        let id = host.env.get_lyquid_id();
334
12
        host.env.node.bartender
335
12
            .as_ref()
336
12
            .ok_or_else(|| lyquid::LyquidError::LyquorRuntime(
"eth_contract function not supported."0
.
into0
()))
?0
337
            .get_eth_contract_address
338
12
            .clone()
339
12
            .oneshot(GetEthContractAddr { pos, id })
340
12
            .await
341
12
            .map_err(|e| lyquid::LyquidError::LyquorRuntime(
format!0
("GetEthContractAddr failed: {e}")))
342
    });
343
344
13
    static ref LVM_SEQUENCE_BACKEND_ID: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>;) -> SequenceBackendID {
345
13
        Ok(host.env.node.sequence_backend_id)
346
    });
347
348
    static ref LVM_FETCH_ORACLE_INFO: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>;
349
            topic: String,
350
            target: OracleTarget,
351
0
            full_config: bool) -> Option<lyquor_primitives::oracle::OracleEpochInfo> {
352
0
        if host.category != lyquor_primitives::StateCategory::Instance {
353
0
            return Err(lyquid::LyquidError::LyquorRuntime(
354
0
                "`fetch_oracle_info` only allowed in instance functions".to_string(),
355
0
            ));
356
0
        }
357
0
        if target.seq_id != host.env.node.sequence_backend_id {
358
0
            return Ok(None);
359
0
        }
360
361
0
        let query = FetchOracleInfo {
362
0
            pos: *host.chain_pos.read().await,
363
0
            topic,
364
0
            target,
365
0
            full_config,
366
        };
367
        // NOTE: Query routing is currently local-only and does not dispatch by
368
        // `query.target.seq_id`. Cross-backend settlement should route by seq_id.
369
0
        host.env
370
0
            .sequencer()
371
0
            .ok_or_else(|| lyquid::LyquidError::LyquorRuntime(
372
0
                "fetch_oracle_info service unavailable.".into(),
373
0
            ))?
374
            .fetch_oracle_info
375
0
            .clone()
376
0
            .oneshot(query)
377
0
            .await
378
0
            .map_err(|e| lyquid::LyquidError::LyquorRuntime(format!("FetchOracleInfo failed: {e}")))
379
    });
380
381
0
    static ref LVM_TRIGGER: HostAPIFunc = lyquor_vm::host_api!((host: Host<Env>; group: String, method: String, input: Vec<u8>, mode: lyquor_primitives::TriggerMode) -> () {
382
        // NOTE: Network and instance functions can both trigger instance functions directly, but
383
        // commit-mode triggers are only valid from network functions. This function should not
384
        // return anything.
385
0
        if matches!(mode, lyquor_primitives::TriggerMode::Commit)
386
0
            && host.category != lyquor_primitives::StateCategory::Network
387
        {
388
0
            return Err(lyquid::LyquidError::LyquorRuntime(
389
0
                "`TriggerMode::Commit` only allowed in network functions".to_string(),
390
0
            ));
391
0
        }
392
393
0
        let version = *host.version.read().await;
394
0
        host.env.lyquid.trigger
395
0
            .as_ref()
396
0
            .ok_or_else(|| lyquid::LyquidError::LyquorRuntime("`trigger` not supported.".into()))?
397
0
            .clone()
398
0
            .oneshot(Trigger {
399
0
                    group,
400
0
                    method,
401
0
                    input,
402
0
                    version,
403
0
                    mode,
404
0
                })
405
0
            .await
406
0
            .map_err(|_| lyquid::LyquidError::LyquorRuntime("Trigger failed: trigger queue is closed".into()))?;
407
0
        Ok(())
408
    });
409
}
410
411
struct Endpoint {
412
    instance: Instance,
413
}
414
415
impl std::fmt::Debug for Endpoint {
416
0
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417
0
        let id = self.instance.id();
418
0
        f.debug_struct("Endpoint").field("instance", &id).finish()
419
0
    }
420
}
421
422
#[derive(Clone)]
423
struct UpcCacheEntry {
424
    barrel: Arc<lyquor_vm::instance::Barrel<Env>>,
425
    cache_ptr: Option<lyquid::upc::CachePtr>,
426
}
427
428
#[async_trait]
429
impl lyquor_upc::Caller for Endpoint {
430
    type Error = lyquor_vm::instance::Error;
431
432
    #[tracing::instrument(level = "trace", skip(context))]
433
    async fn on_prepare(
434
        &self, header: &mut CallHeader, client_params: Bytes, context: &MulticastContext<Self::Error>,
435
    ) -> Result<Vec<NodeID>, Self::Error> {
436
        let input = lyquid::upc::PrepareInput {
437
            client_params: client_params.clone(),
438
        };
439
        let barrel = {
440
            let number = match header.number {
441
                Some(n) => n,
442
                // NOTE: Only execute latest_number() if not given.
443
                None => self.instance.latest_number().await,
444
            };
445
            // Write back exact lyquor number to header
446
            header.number = Some(number);
447
            self.instance
448
                .get_instance_barrel(number)
449
                .await
450
0
                .map_err(|e| lyquor_vm::instance::Error::Setup(e.into()))?
451
        };
452
453
        let call_params = CallParams::builder()
454
            .caller(Address::ZERO)
455
            .method(header.method.clone())
456
            .input(encode_object(&input).into())
457
            .group({
458
                let mut group = lyquor_primitives::GROUP_UPC_PREPARE.to_string();
459
                if let Some(ref suffix) = header.group {
460
                    group.push_str("::");
461
                    group.push_str(suffix);
462
                }
463
                group
464
            })
465
            .build();
466
        let call = barrel.call_func_decoded(
467
            call_params,
468
            Some((
469
                lyquor_vm::scheduler::RunOptions::new(lyquor_vm::scheduler::RunSource::UpcPrepare),
470
                self.instance.scheduler(),
471
            )),
472
        );
473
        let result: Result<lyquid::upc::PrepareOutput, Self::Error> = call.await.map_err(std::convert::Into::into);
474
        let result = result?;
475
476
        {
477
            let mut cache = context.cache.lock().await;
478
            *cache = Some(Box::new(UpcCacheEntry {
479
                barrel,
480
                cache_ptr: result.cache,
481
            }));
482
        }
483
484
        Ok(result.result)
485
    }
486
487
    #[tracing::instrument(level = "trace", skip(context, returned))]
488
    async fn on_return(
489
        &self, header: CallHeader, from: NodeID, context: &MulticastContext<Self::Error>,
490
        returned: Result<RemoteObj, lyquor_upc::Error<Self::Error>>,
491
    ) -> Result<Option<RemoteObj>, lyquor_upc::Error<Self::Error>> {
492
        let id = context.id;
493
        let returned = returned?;
494
495
        let mut cache = context.cache.lock().await;
496
        if cache.is_none() {
497
            let number = match header.number {
498
                Some(n) => n,
499
                // NOTE: Only execute latest_number() if not given.
500
                None => self.instance.latest_number().await,
501
            };
502
            *cache = Some(Box::new(UpcCacheEntry {
503
                barrel: self
504
                    .instance
505
                    .get_instance_barrel(number)
506
                    .await
507
                    .map_err(lyquor_upc::Error::Call)?,
508
                cache_ptr: None,
509
            }));
510
        }
511
        let cache_entry = cache
512
            .as_mut()
513
            .unwrap()
514
            .as_mut()
515
            .downcast_mut::<UpcCacheEntry>()
516
            .unwrap();
517
518
        let input = lyquid::upc::ResponseInput {
519
            from,
520
            id,
521
            returned: returned.to_encoded().into(),
522
            cache: cache_entry.cache_ptr,
523
        };
524
        let call_params = CallParams::builder()
525
            .caller(Address::ZERO)
526
            .method(header.method)
527
            .input(encode_object(&input).into())
528
            .group({
529
                let mut group = lyquor_primitives::GROUP_UPC_RESP.to_string();
530
                if let Some(ref suffix) = header.group {
531
                    group.push_str("::");
532
                    group.push_str(suffix);
533
                }
534
                group
535
            })
536
            .build();
537
        let call = cache_entry.barrel.call_func_decoded(
538
            call_params,
539
            Some((
540
                lyquor_vm::scheduler::RunOptions::new(lyquor_vm::scheduler::RunSource::UpcResponse),
541
                self.instance.scheduler(),
542
            )),
543
        );
544
        let result: Result<lyquid::upc::ResponseOutput, lyquor_vm::instance::Error> =
545
            call.await.map_err(lyquor_vm::instance::Error::Barrel);
546
        let result = match result {
547
            Err(lyquor_vm::instance::Error::Barrel(lyquor_vm::barrel::Error::FuncNotFound)) => {
548
                // by default if upc_response event handler is not implemented, it will just
549
                // return the first seen result
550
                return Ok(Some(returned));
551
            }
552
            r => r,
553
        };
554
        let result = result.map_err(lyquor_upc::Error::Call)?;
555
556
        Ok(match result {
557
            lyquid::upc::ResponseOutput::Continue(cache) => {
558
                cache_entry.cache_ptr = cache;
559
                None
560
            }
561
            lyquid::upc::ResponseOutput::Return(result) => {
562
                *cache = None;
563
                Some(RemoteObj::from_encoded(result.into()))
564
            }
565
        })
566
    }
567
}
568
569
#[async_trait]
570
impl lyquor_upc::Callee for Endpoint {
571
    type Error = lyquor_vm::instance::Error;
572
573
    #[tracing::instrument(level = "trace", skip(input))]
574
    async fn on_call(&self, header: CallHeader, from: NodeID, input: RemoteObj) -> Result<RemoteObj, Self::Error> {
575
        let input = encode_object(&lyquid::upc::RequestInput {
576
            from,
577
            id: 0,
578
            input: input.as_encoded().unwrap().into(),
579
        })
580
        .into();
581
        let call = {
582
            // TODO: Now, header should always have LYQNUM present. However this leads to a problem
583
            // which the LYQNUM may not be a valid one on callee(which is me here). For now let's
584
            // check the existance of this number and if not let's go with the latest.
585
            let version = match header.number {
586
                Some(n) => n,
587
                // NOTE: Only execute latest_number() if not given.
588
                None => self.instance.latest_number().await,
589
            };
590
            let number = if self.instance.check_version(version).await {
591
                version
592
            } else {
593
                self.instance.latest_number().await
594
            };
595
596
            self.instance
597
                .call_instance_func(
598
                    number,
599
                    CallParams::builder()
600
                        .caller(Address::ZERO)
601
                        .method(header.method)
602
                        .input(input)
603
                        .group({
604
                            let mut group = lyquor_primitives::GROUP_UPC_REQ.to_string();
605
                            if let Some(ref suffix) = header.group {
606
                                group.push_str("::");
607
                                group.push_str(suffix);
608
                            }
609
                            group
610
                        })
611
                        .build(),
612
                    lyquor_vm::scheduler::RunOptions::new(lyquor_vm::scheduler::RunSource::UpcRequest),
613
                )
614
                .await
615
        };
616
        let result: lyquid::upc::RequestOutput = call.await?;
617
        Ok(RemoteObj::from_encoded(result.into()))
618
    }
619
}
620
621
/// Hosting-owned VM runtime plus UPC registration state.
622
pub struct VmRuntime {
623
    vm_engine: lyquor_vm::VmEngine,
624
    upc: lyquor_upc::UPC<lyquor_vm::instance::Error>,
625
    sequencer: Option<Sequencer>,
626
    egress_config: EgressConfig,
627
    sequence_backend_id: SequenceBackendID,
628
}
629
630
impl VmRuntime {
631
    /// Create a VM runtime bound to a network hub and optional sequencer services.
632
41
    pub fn new(
633
41
        runtime: tokio::runtime::Handle, network: Arc<lyquor_net::hub::Hub>, sequencer: Option<Sequencer>,
634
41
        egress_config: EgressConfig, sequence_backend_id: SequenceBackendID,
635
41
    ) -> Result<Self, VmRuntimeError> {
636
        Ok(Self {
637
41
            vm_engine: lyquor_vm::VmEngine::new(runtime)
?0
,
638
41
            upc: lyquor_upc::UPC::new(network),
639
41
            sequencer,
640
41
            egress_config,
641
41
            sequence_backend_id,
642
        })
643
41
    }
644
645
    /// Replace sequencer services exposed to subsequently created instances.
646
0
    pub fn set_sequencer(&mut self, sequencer: Option<Sequencer>) {
647
0
        self.sequencer = sequencer;
648
0
    }
649
650
    /// Return a UPC requester for hosted Lyquid calls.
651
23
    pub fn requester(&self) -> lyquor_upc::Requester<lyquor_vm::instance::Error> {
652
23
        self.upc.requester()
653
23
    }
654
655
    /// Create and register a hosted VM instance with all Lyquor host APIs.
656
48
    pub async fn add_instance(
657
48
        &mut self, id: LyquidID, stores: lyquor_vm::InstanceStores, console: Option<mpsc::Sender<ConsoleOutput>>,
658
48
        bartender: Option<Bartender>, trigger: Option<TriggerService>,
659
48
        image_source: Option<lyquor_vm::instance::ImageSource>,
660
48
    ) -> Result<Instance, VmRuntimeError> {
661
48
        let upc = self.upc.requester();
662
48
        let node_id = *self.upc.node_id();
663
48
        let sig_provider = Arc::new(crypto::SigProvider::new(self.upc.signing_key().as_ref().clone()));
664
48
        let egress = Egress::new(self.egress_config.clone());
665
48
        let builder = self
666
48
            .vm_engine
667
48
            .instance_builder(
668
48
                id,
669
48
                stores,
670
48
                Env {
671
48
                    node: NodeEnv {
672
48
                        upc,
673
48
                        node_id,
674
48
                        sequencer: self.sequencer.clone(),
675
48
                        sig_provider,
676
48
                        bartender,
677
48
                        sequence_backend_id: self.sequence_backend_id,
678
48
                    },
679
48
                    lyquid: LyquidEnv {
680
48
                        lyquid_id: id,
681
48
                        console,
682
48
                        egress,
683
48
                        trigger,
684
48
                    },
685
48
                },
686
48
            )
687
48
            .await
?0
;
688
48
        let 
instance47
= builder
689
48
            .with_image_source(image_source)
690
48
            .host_api("console_output", LVM_CONSOLE.clone())
691
48
            .host_api("universal_procedural_call", LVM_UPC.clone())
692
48
            .host_api("inter_lyquid_call", LVM_INTER_LYQUID_CALL.clone())
693
48
            .host_api("submit_call", LVM_SUBMIT_CALL.clone())
694
48
            .host_api("sign", LVM_SIGN.clone())
695
48
            .host_api("verify", LVM_VERIFY.clone())
696
48
            .host_api("random_bytes", LVM_RNG.clone())
697
48
            .host_api("systime", LVM_GET_TIME.clone())
698
48
            .host_api("http_request", LVM_HTTP_REQUEST.clone())
699
48
            .host_api("get_ed25519_qxy", LVM_GET_ED25519_QXY.clone())
700
48
            .host_api("get_address_by_ed25519", LVM_GET_ADDRESS_BY_ED25519.clone())
701
48
            .host_api("get_ed25519_by_address", LVM_GET_ED25519_BY_ADDRESS.clone())
702
48
            .host_api("eth_contract", LVM_ETH_CONTRACT.clone())
703
48
            .host_api("sequence_backend_id", LVM_SEQUENCE_BACKEND_ID.clone())
704
48
            .host_api("fetch_oracle_info", LVM_FETCH_ORACLE_INFO.clone())
705
48
            .host_api("trigger", LVM_TRIGGER.clone())
706
48
            .build()
707
48
            .await
708
48
            .map_err(lyquor_vm::Error::from)
?1
;
709
710
47
        let instance = Arc::new(instance);
711
47
        self.upc
712
47
            .register_caller(
713
47
                &id,
714
47
                Endpoint {
715
47
                    instance: instance.clone(),
716
47
                },
717
47
            )
718
47
            .await
?0
;
719
47
        if let Err(
err0
) = self
720
47
            .upc
721
47
            .register_callee(
722
47
                &id,
723
47
                Endpoint {
724
47
                    instance: instance.clone(),
725
47
                },
726
            )
727
47
            .await
728
        {
729
0
            self.upc.deregister_caller(&id);
730
0
            return Err(err.into());
731
47
        }
732
47
        Ok(instance)
733
48
    }
734
735
    /// Deregister a hosted instance from UPC caller and callee tables.
736
7
    pub fn remove_instance(&mut self, id: &LyquidID) {
737
7
        self.upc.deregister_caller(id);
738
7
        self.upc.deregister_callee(id);
739
7
    }
740
}