jade_testing/
exec.rs

1//! Execution API of JAM VM
2
3use crate::{Jam, key};
4use anyhow::Result;
5use service::{
6    ServiceId,
7    api::{
8        AccumulateArgs, AccumulateState, Accumulated, AuthorizeArgs, Reason, RefineArgs,
9        ValidatorData,
10    },
11    service::{
12        Privileges, RefineLoad, ServiceAccount, WorkExecResult, WorkPackage, WorkResult,
13        result::Executed,
14    },
15    vm::Operand,
16};
17use std::collections::BTreeMap;
18
19/// The result of an execution
20#[derive(Debug, Default)]
21pub struct ExecutionInfo {
22    /// The refine gas used
23    pub refine_gas: u64,
24
25    /// The accumulate gas used
26    pub accumulate_gas: u64,
27
28    /// The account changes
29    pub accounts: BTreeMap<ServiceId, ServiceAccount>,
30}
31
32impl ExecutionInfo {
33    /// Create a new execution info
34    pub fn new(acc: Vec<Accumulated>) -> Self {
35        let mut info = Self::default();
36        for acc in acc.iter() {
37            info.accumulate_gas += acc.gas;
38
39            // FIXME: need to merge account data
40            info.accounts = acc.context.accounts.clone();
41        }
42
43        info
44    }
45
46    /// Get a storage of an account
47    pub fn get_storage<V: serde::de::DeserializeOwned>(
48        &self,
49        service: ServiceId,
50        key: &[u8],
51    ) -> Option<V> {
52        let account = self.accounts.get(&service)?;
53        let vkey = key.to_vec();
54        let key = key::storage(service, &vkey);
55        let encoded = account.storage.get(key.as_ref())?;
56        codec::decode(encoded).ok()
57    }
58}
59
60impl Jam {
61    /// Execute a work item directly
62    ///
63    /// TODO: introduce better execution result
64    pub fn execute(&mut self, service: ServiceId, payload: Vec<u8>) -> Result<ExecutionInfo> {
65        let package = self.send(service, payload)?;
66        let result = self.refine(&package)?;
67        Ok(ExecutionInfo::new(self.accumulate(result)?))
68    }
69
70    /// Authorize the work package
71    #[tracing::instrument(name = "authorize", skip_all)]
72    pub fn authorize(&mut self, work: &WorkPackage, core_idx: u16) -> Result<Executed> {
73        tracing::debug!(
74            "service={}, code=0x{}",
75            work.auth_code_host,
76            hex::encode(work.auth_code_hash)
77        );
78
79        spacevm::authorize(AuthorizeArgs {
80            package: work.clone(),
81            core_idx,
82            accounts: self.chain.accounts.clone(),
83            timeslot: self.chain.best.slot,
84        })
85    }
86
87    /// Refine the work package
88    ///
89    /// NOTE: run refine for all work items
90    #[tracing::instrument(name = "refine", skip_all)]
91    pub fn refine(&mut self, work: &WorkPackage) -> Result<Vec<WorkResult>> {
92        tracing::debug!("package: items={}", work.items.len());
93        if work.items.is_empty() {
94            anyhow::bail!("no work items");
95        }
96
97        let mut result = Vec::new();
98        for (index, item) in work.items.iter().enumerate() {
99            let refined = spacevm::refine(RefineArgs {
100                accounts: self.chain.accounts.clone(),
101                core: 0,
102                index: 0,
103                package: work.clone(),
104                export_offset: 0,
105                timeslot: self.chain.best.slot,
106                auth_output: Default::default(),
107                all_imports: Default::default(),
108            })?;
109
110            if !matches!(refined.executed.exec, WorkExecResult::Ok(_)) {
111                return Err(anyhow::anyhow!(
112                    "work item {index} refine failed: {:?}",
113                    refined.executed.exec
114                ));
115            }
116
117            result.push(WorkResult {
118                service_id: item.service,
119                code_hash: item.code_hash,
120                payload_hash: Default::default(),
121                accumulate_gas: Default::default(),
122                result: refined.executed.exec,
123                refine_load: RefineLoad {
124                    gas_used: refined.executed.gas,
125                    imports: Default::default(),
126                    extrinsic_count: Default::default(),
127                    extrinsic_size: Default::default(),
128                    exports: Default::default(),
129                },
130            });
131        }
132
133        Ok(result)
134    }
135
136    /// Accumulate the work package
137    ///
138    /// 1. convert work package to work report
139    /// 2. run accumulate for all work items
140    /// 3. return the accumulated result
141    #[tracing::instrument(name = "accumulate", skip_all)]
142    pub fn accumulate(&mut self, results: Vec<WorkResult>) -> Result<Vec<Accumulated>> {
143        tracing::debug!("work: items={}", results.len());
144        if results.is_empty() {
145            anyhow::bail!("no results");
146        }
147
148        let accounts = self.chain.accounts.clone();
149        let mut state = AccumulateState {
150            accounts,
151            validators: [ValidatorData {
152                bandersnatch: Default::default(),
153                ed25519: Default::default(),
154                bls: [0; 144],
155                metadata: [0; 128],
156            }; 6],
157            authorization: Default::default(),
158            privileges: Privileges::default(),
159            entropy: Default::default(),
160        };
161
162        let service = results.first().expect("checked").service_id;
163        let operands = {
164            let mut operands = vec![];
165            for work in results.iter() {
166                operands.push(Operand {
167                    package: Default::default(),
168                    exports_root: Default::default(),
169                    authorizer_hash: Default::default(),
170                    auth_output: Default::default(),
171                    payload: work.payload_hash,
172                    gas: work.accumulate_gas,
173                    data: work.result.clone(),
174                });
175            }
176            operands
177        };
178
179        // run accumulation
180        let accumulated = spacevm::accumulate(AccumulateArgs {
181            context: state,
182            timeslot: self.chain.best.slot,
183            service,
184            gas: 1_000_000_000,
185            operands,
186        })?;
187
188        if !matches!(accumulated.reason, Reason::Halt) {
189            anyhow::bail!("accumulate failed: {:?}", accumulated.reason);
190        }
191        state = accumulated.context.clone();
192        self.chain.accounts = state.accounts;
193        Ok(vec![accumulated])
194    }
195}