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    IOTA_FRAMEWORK_PACKAGE_ID, IOTA_SYSTEM_PACKAGE_ID, MOVE_STDLIB_PACKAGE_ID, STARDUST_PACKAGE_ID,
15    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    STARDUST_PACKAGE_ID,
77];
78
79/// Returns the list of system packages in the order they should be published.
80/// If the protocol version is < 9 then include also the bridge package.
81pub fn get_system_package_publish_order(protocol_version: u64) -> Vec<ObjectID> {
82    let mut publish_order = SYSTEM_PACKAGE_PUBLISH_ORDER.to_vec();
83    if protocol_version < 9 {
84        publish_order.insert(3, iota_types::GENESIS_BRIDGE_PACKAGE_ID);
85    }
86    publish_order
87}
88
89pub fn load_bytecode_snapshot_manifest() -> SnapshotManifest {
90    let Ok(bytes) = fs::read(manifest_path()) else {
91        return SnapshotManifest::default();
92    };
93    serde_json::from_slice::<SnapshotManifest>(&bytes)
94        .expect("Could not deserialize SnapshotManifest")
95}
96
97pub fn update_bytecode_snapshot_manifest(
98    git_revision: &str,
99    version: u64,
100    files: Vec<SnapshotPackage>,
101) {
102    let mut snapshot = load_bytecode_snapshot_manifest();
103
104    snapshot.insert(
105        version,
106        Snapshot {
107            git_revision: git_revision.to_string(),
108            packages: files,
109        },
110    );
111
112    let json =
113        serde_json::to_string_pretty(&snapshot).expect("Could not serialize SnapshotManifest");
114    fs::write(manifest_path(), json).expect("Could not update manifest file");
115}
116
117pub fn load_bytecode_snapshot(protocol_version: u64) -> anyhow::Result<Vec<SystemPackage>> {
118    let snapshot_path = snapshot_path_for_version(protocol_version)?;
119    let mut snapshots: BTreeMap<ObjectID, SystemPackage> = fs::read_dir(&snapshot_path)?
120        .flatten()
121        .map(|entry| {
122            let file_name = entry.file_name().to_str().unwrap().to_string();
123            let mut file = fs::File::open(snapshot_path.clone().join(file_name))?;
124            let mut buffer = Vec::new();
125            file.read_to_end(&mut buffer)?;
126            let package: SystemPackage = bcs::from_bytes(&buffer)?;
127            Ok((package.id, package))
128        })
129        .collect::<anyhow::Result<_>>()?;
130
131    // system packages need to be restored in a specific order
132    let snapshots_publish_order = get_system_package_publish_order(protocol_version);
133    assert!(snapshots.len() <= snapshots_publish_order.len());
134    let mut snapshot_objects = Vec::new();
135    for package_id in &snapshots_publish_order {
136        if let Some(object) = snapshots.remove(package_id) {
137            snapshot_objects.push(object);
138        }
139    }
140    Ok(snapshot_objects)
141}
142
143pub fn manifest_path() -> PathBuf {
144    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("manifest.json")
145}
146
147/// Given a protocol version:
148/// * The path to the snapshot directory for that version is returned, if it
149///   exists.
150/// * If the version is greater than the latest snapshot version, then
151///   `Ok(None)` is returned.
152/// * If the version does not exist, but there are snapshots present with
153///   versions greater than `version`, then the smallest snapshot number greater
154///   than `version` is returned.
155fn snapshot_path_for_version(version: u64) -> anyhow::Result<PathBuf> {
156    let snapshot_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("bytecode_snapshot");
157    let mut snapshots = BTreeSet::new();
158
159    for entry in fs::read_dir(&snapshot_dir)? {
160        let entry = entry?;
161        let path = entry.path();
162        if path.is_dir() {
163            if let Some(snapshot_number) = path
164                .file_name()
165                .and_then(|n| n.to_str())
166                .and_then(|n| n.parse::<u64>().ok())
167            {
168                snapshots.insert(snapshot_number);
169            }
170        }
171    }
172
173    snapshots
174        .range(version..)
175        .next()
176        .map(|v| snapshot_dir.join(v.to_string()))
177        .ok_or_else(|| anyhow::anyhow!("No snapshot found for version {}", version))
178}