sui_adapter_v1/programmable_transactions/
linkage_view.rs1use std::{
5 cell::RefCell,
6 collections::{hash_map::Entry, BTreeMap, HashMap, HashSet},
7 str::FromStr,
8};
9
10use crate::execution_value::SuiResolver;
11use move_core_types::{
12 account_address::AccountAddress,
13 identifier::{IdentStr, Identifier},
14 language_storage::ModuleId,
15 resolver::ModuleResolver,
16};
17use move_vm_types::data_store::LinkageResolver;
18use sui_types::storage::{get_module, PackageObject};
19use sui_types::{
20 base_types::ObjectID,
21 error::{ExecutionError, SuiError, SuiResult},
22 move_package::{MovePackage, TypeOrigin, UpgradeInfo},
23 storage::BackingPackageStore,
24};
25
26pub struct LinkageView<'state> {
29 resolver: Box<dyn SuiResolver + 'state>,
31 linkage_info: Option<LinkageInfo>,
33 type_origin_cache: RefCell<HashMap<ModuleId, HashMap<Identifier, AccountAddress>>>,
41 past_contexts: RefCell<HashSet<ObjectID>>,
44}
45
46#[derive(Debug)]
47pub struct LinkageInfo {
48 storage_id: AccountAddress,
49 runtime_id: AccountAddress,
50 link_table: BTreeMap<ObjectID, UpgradeInfo>,
51}
52
53pub struct SavedLinkage(LinkageInfo);
54
55impl<'state> LinkageView<'state> {
56 pub fn new(resolver: Box<dyn SuiResolver + 'state>) -> Self {
57 Self {
58 resolver,
59 linkage_info: None,
60 type_origin_cache: RefCell::new(HashMap::new()),
61 past_contexts: RefCell::new(HashSet::new()),
62 }
63 }
64
65 pub fn reset_linkage(&mut self) {
66 self.linkage_info = None;
67 }
68
69 pub fn has_linkage(&self, context: ObjectID) -> bool {
72 self.linkage_info
73 .as_ref()
74 .is_some_and(|l| l.storage_id == *context)
75 }
76
77 pub fn steal_linkage(&mut self) -> Option<SavedLinkage> {
79 Some(SavedLinkage(self.linkage_info.take()?))
80 }
81
82 pub fn restore_linkage(&mut self, saved: Option<SavedLinkage>) -> Result<(), ExecutionError> {
84 let Some(SavedLinkage(saved)) = saved else {
85 return Ok(());
86 };
87
88 if let Some(existing) = &self.linkage_info {
89 invariant_violation!(
90 "Attempt to overwrite linkage by restoring: {saved:#?} \
91 Existing linkage: {existing:#?}",
92 )
93 }
94
95 self.linkage_info = Some(saved);
98 Ok(())
99 }
100
101 pub fn set_linkage(&mut self, context: &MovePackage) -> Result<AccountAddress, ExecutionError> {
105 if let Some(existing) = &self.linkage_info {
106 invariant_violation!(
107 "Attempt to overwrite linkage info with context from {}. \
108 Existing linkage: {existing:#?}",
109 context.id(),
110 )
111 }
112
113 let linkage = LinkageInfo::from(context);
114 let storage_id = context.id();
115 let runtime_id = linkage.runtime_id;
116 self.linkage_info = Some(linkage);
117
118 if !self.past_contexts.borrow_mut().insert(storage_id) {
119 return Ok(runtime_id);
120 }
121
122 for TypeOrigin {
126 module_name,
127 datatype_name: struct_name,
128 package: defining_id,
129 } in context.type_origin_table()
130 {
131 let Ok(module_name) = Identifier::from_str(module_name) else {
132 invariant_violation!("Module name isn't an identifier: {module_name}");
133 };
134
135 let Ok(struct_name) = Identifier::from_str(struct_name) else {
136 invariant_violation!("Struct name isn't an identifier: {struct_name}");
137 };
138
139 let runtime_id = ModuleId::new(runtime_id, module_name);
140 self.add_type_origin(runtime_id, struct_name, *defining_id)?;
141 }
142
143 Ok(runtime_id)
144 }
145
146 pub fn original_package_id(&self) -> Option<AccountAddress> {
147 Some(self.linkage_info.as_ref()?.runtime_id)
148 }
149
150 fn get_cached_type_origin(
151 &self,
152 runtime_id: &ModuleId,
153 struct_: &IdentStr,
154 ) -> Option<AccountAddress> {
155 self.type_origin_cache
156 .borrow()
157 .get(runtime_id)?
158 .get(struct_)
159 .cloned()
160 }
161
162 fn add_type_origin(
163 &self,
164 runtime_id: ModuleId,
165 struct_: Identifier,
166 defining_id: ObjectID,
167 ) -> Result<(), ExecutionError> {
168 let mut cache = self.type_origin_cache.borrow_mut();
169 let module_cache = cache.entry(runtime_id.clone()).or_default();
170
171 match module_cache.entry(struct_) {
172 Entry::Vacant(entry) => {
173 entry.insert(*defining_id);
174 }
175
176 Entry::Occupied(entry) => {
177 if entry.get() != &*defining_id {
178 invariant_violation!(
179 "Conflicting defining ID for {}::{}: {} and {}",
180 runtime_id,
181 entry.key(),
182 defining_id,
183 entry.get(),
184 );
185 }
186 }
187 }
188
189 Ok(())
190 }
191
192 pub(crate) fn link_context(&self) -> AccountAddress {
193 self.linkage_info
194 .as_ref()
195 .map_or(AccountAddress::ZERO, |l| l.storage_id)
196 }
197
198 pub(crate) fn relocate(&self, module_id: &ModuleId) -> Result<ModuleId, SuiError> {
199 let Some(linkage) = &self.linkage_info else {
200 invariant_violation!("No linkage context set while relocating {module_id}.")
201 };
202
203 if module_id.address() == &linkage.runtime_id {
206 return Ok(ModuleId::new(
207 linkage.storage_id,
208 module_id.name().to_owned(),
209 ));
210 }
211
212 let runtime_id = ObjectID::from_address(*module_id.address());
213 let Some(upgrade) = linkage.link_table.get(&runtime_id) else {
214 invariant_violation!(
215 "Missing linkage for {runtime_id} in context {}, runtime_id is {}",
216 linkage.storage_id,
217 linkage.runtime_id
218 );
219 };
220
221 Ok(ModuleId::new(
222 upgrade.upgraded_id.into(),
223 module_id.name().to_owned(),
224 ))
225 }
226
227 pub(crate) fn defining_module(
228 &self,
229 runtime_id: &ModuleId,
230 struct_: &IdentStr,
231 ) -> Result<ModuleId, SuiError> {
232 if self.linkage_info.is_none() {
233 invariant_violation!(
234 "No linkage context set for defining module query on {runtime_id}::{struct_}."
235 )
236 }
237
238 if let Some(cached) = self.get_cached_type_origin(runtime_id, struct_) {
239 return Ok(ModuleId::new(cached, runtime_id.name().to_owned()));
240 }
241
242 let storage_id = ObjectID::from(*self.relocate(runtime_id)?.address());
243 let Some(package) = self.resolver.get_package_object(&storage_id)? else {
244 invariant_violation!("Missing dependent package in store: {storage_id}",)
245 };
246
247 for TypeOrigin {
248 module_name,
249 datatype_name: struct_name,
250 package,
251 } in package.move_package().type_origin_table()
252 {
253 if module_name == runtime_id.name().as_str() && struct_name == struct_.as_str() {
254 self.add_type_origin(runtime_id.clone(), struct_.to_owned(), *package)?;
255 return Ok(ModuleId::new(**package, runtime_id.name().to_owned()));
256 }
257 }
258
259 invariant_violation!(
260 "{runtime_id}::{struct_} not found in type origin table in {storage_id} (v{})",
261 package.move_package().version(),
262 )
263 }
264}
265
266impl From<&MovePackage> for LinkageInfo {
267 fn from(package: &MovePackage) -> Self {
268 Self {
269 storage_id: package.id().into(),
270 runtime_id: package.original_package_id().into(),
271 link_table: package.linkage_table().clone(),
272 }
273 }
274}
275
276impl LinkageResolver for LinkageView<'_> {
277 type Error = SuiError;
278
279 fn link_context(&self) -> AccountAddress {
280 LinkageView::link_context(self)
281 }
282
283 fn relocate(&self, module_id: &ModuleId) -> Result<ModuleId, Self::Error> {
284 LinkageView::relocate(self, module_id)
285 }
286
287 fn defining_module(
288 &self,
289 runtime_id: &ModuleId,
290 struct_: &IdentStr,
291 ) -> Result<ModuleId, Self::Error> {
292 LinkageView::defining_module(self, runtime_id, struct_)
293 }
294}
295
296impl ModuleResolver for LinkageView<'_> {
299 type Error = SuiError;
300
301 fn get_module(&self, id: &ModuleId) -> Result<Option<Vec<u8>>, Self::Error> {
302 get_module(self, id)
303 }
304
305 fn get_packages_static<const N: usize>(
306 &self,
307 _ids: [AccountAddress; N],
308 ) -> Result<[Option<move_core_types::resolver::SerializedPackage>; N], Self::Error> {
309 unreachable!("v1 get_packages_static should not be called on LinkageView")
310 }
311
312 fn get_packages<'a>(
313 &self,
314 _ids: impl ExactSizeIterator<Item = &'a AccountAddress>,
315 ) -> Result<Vec<Option<move_core_types::resolver::SerializedPackage>>, Self::Error> {
316 unreachable!("v1 get_packages should not be called on LinkageView")
317 }
318}
319
320impl BackingPackageStore for LinkageView<'_> {
321 fn get_package_object(&self, package_id: &ObjectID) -> SuiResult<Option<PackageObject>> {
322 self.resolver.get_package_object(package_id)
323 }
324}