iota_framework_snapshot/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    collections::{BTreeMap, BTreeSet},
7    fs,
8    io::Read,
9    path::PathBuf,
10};
11
12use iota_framework::{SystemPackage, SystemPackageMetadata};
13use iota_types::{
14    BRIDGE_PACKAGE_ID, IOTA_FRAMEWORK_PACKAGE_ID, IOTA_SYSTEM_PACKAGE_ID, MOVE_STDLIB_PACKAGE_ID,
15    STARDUST_PACKAGE_ID, base_types::ObjectID,
16};
17use serde::{Deserialize, Serialize};
18
19pub type SnapshotManifest = BTreeMap<u64, Snapshot>;
20
21/// Encapsulation of an entry in the manifest file corresponding to a single
22/// version of the system packages.
23// Note: the [Snapshot] and [SnapshotPackage] types are similar to the
24// [iota_framework::{SystemPackageMetadata, SystemPackage}] types,
25// and also to the [iota::framework_versions::{FrameworkVersion, FrameworkPackage}] types.
26// They are sort of a stepping stone from one to the other - the [iota_framework] types contain
27// additional information about the compiled bytecode of the package, while the
28// [framework_versions] types do not contain information about the object IDs of the packages.
29//
30// These types serve as a kind of stepping stone; they are constructed from the [iota_framework]
31// types and serialized in the manifest, and then the build script for the [iota] crate reads them
32// from the manifest file and encodes them in the `iota` binary. A little information is dropped in
33// each of these steps.
34#[derive(Serialize, Deserialize)]
35pub struct Snapshot {
36    /// Git revision that this snapshot is taken on.
37    pub git_revision: String,
38
39    /// List of system packages in this version.
40    pub packages: Vec<SnapshotPackage>,
41}
42
43/// Entry in the manifest file corresponding to a specific version of a specific
44/// system package.
45#[derive(Serialize, Deserialize)]
46pub struct SnapshotPackage {
47    /// Name of the package (e.g. "MoveStdLib").
48    pub name: String,
49    /// Path to the package in the monorepo (e.g.
50    /// "crates/iota-framework/packages/move-stdlib").
51    pub path: String,
52    /// Object ID of the published package.
53    pub id: ObjectID,
54}
55
56impl Snapshot {
57    pub fn package_ids(&self) -> impl Iterator<Item = ObjectID> + '_ {
58        self.packages.iter().map(|p| p.id)
59    }
60}
61
62impl SnapshotPackage {
63    pub fn from_system_package_metadata(value: &SystemPackageMetadata) -> Self {
64        Self {
65            name: value.name.clone(),
66            path: value.path.clone(),
67            id: value.compiled.id,
68        }
69    }
70}
71
72const SYSTEM_PACKAGE_PUBLISH_ORDER: &[ObjectID] = &[
73    MOVE_STDLIB_PACKAGE_ID,
74    IOTA_FRAMEWORK_PACKAGE_ID,
75    IOTA_SYSTEM_PACKAGE_ID,
76    BRIDGE_PACKAGE_ID,
77    STARDUST_PACKAGE_ID,
78];
79
80pub fn load_bytecode_snapshot_manifest() -> SnapshotManifest {
81    let Ok(bytes) = fs::read(manifest_path()) else {
82        return SnapshotManifest::default();
83    };
84    serde_json::from_slice::<SnapshotManifest>(&bytes)
85        .expect("Could not deserialize SnapshotManifest")
86}
87
88pub fn update_bytecode_snapshot_manifest(
89    git_revision: &str,
90    version: u64,
91    files: Vec<SnapshotPackage>,
92) {
93    let mut snapshot = load_bytecode_snapshot_manifest();
94
95    snapshot.insert(
96        version,
97        Snapshot {
98            git_revision: git_revision.to_string(),
99            packages: files,
100        },
101    );
102
103    let json =
104        serde_json::to_string_pretty(&snapshot).expect("Could not serialize SnapshotManifest");
105    fs::write(manifest_path(), json).expect("Could not update manifest file");
106}
107
108pub fn load_bytecode_snapshot(protocol_version: u64) -> anyhow::Result<Vec<SystemPackage>> {
109    let snapshot_path = snapshot_path_for_version(protocol_version)?;
110    let mut snapshots: BTreeMap<ObjectID, SystemPackage> = fs::read_dir(&snapshot_path)?
111        .flatten()
112        .map(|entry| {
113            let file_name = entry.file_name().to_str().unwrap().to_string();
114            let mut file = fs::File::open(snapshot_path.clone().join(file_name))?;
115            let mut buffer = Vec::new();
116            file.read_to_end(&mut buffer)?;
117            let package: SystemPackage = bcs::from_bytes(&buffer)?;
118            Ok((package.id, package))
119        })
120        .collect::<anyhow::Result<_>>()?;
121
122    // system packages need to be restored in a specific order
123    assert!(snapshots.len() <= SYSTEM_PACKAGE_PUBLISH_ORDER.len());
124    let mut snapshot_objects = Vec::new();
125    for package_id in SYSTEM_PACKAGE_PUBLISH_ORDER {
126        if let Some(object) = snapshots.remove(package_id) {
127            snapshot_objects.push(object);
128        }
129    }
130    Ok(snapshot_objects)
131}
132
133pub fn manifest_path() -> PathBuf {
134    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("manifest.json")
135}
136
137/// Given a protocol version:
138/// * The path to the snapshot directory for that version is returned, if it
139///   exists.
140/// * If the version is greater than the latest snapshot version, then
141///   `Ok(None)` is returned.
142/// * If the version does not exist, but there are snapshots present with
143///   versions greater than `version`, then the smallest snapshot number greater
144///   than `version` is returned.
145fn snapshot_path_for_version(version: u64) -> anyhow::Result<PathBuf> {
146    let snapshot_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("bytecode_snapshot");
147    let mut snapshots = BTreeSet::new();
148
149    for entry in fs::read_dir(&snapshot_dir)? {
150        let entry = entry?;
151        let path = entry.path();
152        if path.is_dir() {
153            if let Some(snapshot_number) = path
154                .file_name()
155                .and_then(|n| n.to_str())
156                .and_then(|n| n.parse::<u64>().ok())
157            {
158                snapshots.insert(snapshot_number);
159            }
160        }
161    }
162
163    snapshots
164        .range(version..)
165        .next()
166        .map(|v| snapshot_dir.join(v.to_string()))
167        .ok_or_else(|| anyhow::anyhow!("No snapshot found for version {}", version))
168}