|
5 | 5 | use crate::{ |
6 | 6 | cache::move_cache::{MoveCache, Package}, |
7 | 7 | dev_utils::{ |
8 | | - compilation_utils::{compile_packages, compile_packages_in_file}, |
| 8 | + compilation_utils::{ |
| 9 | + compile_packages, compile_packages_in_file, expect_modules, make_base_path, |
| 10 | + }, |
9 | 11 | in_memory_test_adapter::InMemoryTestAdapter, |
| 12 | + storage::StoredPackage, |
10 | 13 | vm_test_adapter::VMTestAdapter, |
11 | 14 | }, |
12 | 15 | runtime::{package_resolution::resolve_packages, telemetry::TransactionTelemetryContext}, |
13 | | - shared::{linkage_context::LinkageContext, types::VersionId}, |
| 16 | + shared::{ |
| 17 | + linkage_context::LinkageContext, |
| 18 | + types::{OriginalId, VersionId}, |
| 19 | + }, |
| 20 | +}; |
| 21 | +use indexmap::IndexMap; |
| 22 | +use move_binary_format::{CompiledModule, errors::VMResult}; |
| 23 | +use move_compiler::Compiler; |
| 24 | +use move_core_types::{ |
| 25 | + account_address::AccountAddress, |
| 26 | + identifier::Identifier, |
| 27 | + resolver::{IntraPackageName, ModuleResolver}, |
14 | 28 | }; |
15 | | -use move_binary_format::errors::VMResult; |
16 | | -use move_core_types::{account_address::AccountAddress, resolver::ModuleResolver}; |
17 | 29 | use move_vm_config::runtime::VMConfig; |
18 | 30 | use std::collections::BTreeMap; |
19 | 31 | use std::sync::Arc; |
@@ -723,165 +735,166 @@ fn cache_reuses_packages_across_linkage_contexts() { |
723 | 735 | assert_eq!(pkg1_before.loaded_types_len(), pkg1_reloaded.loaded_types_len()); |
724 | 736 | } |
725 | 737 |
|
726 | | -// Test that we properly publish and relink (and reuse) packages. |
727 | | -// FIXME FIXME FIXME |
728 | | -#[test] |
729 | | -fn relink() { |
730 | | - /* |
731 | | - let mut adapter = InMemoryTestAdapter::new(); |
732 | | -
|
733 | | - let st_c_v1_addr = AccountAddress::from_hex_literal("0x42").unwrap(); |
734 | | - let st_b_v1_addr = AccountAddress::from_hex_literal("0x43").unwrap(); |
735 | | -
|
736 | | - let c_original_addr = AccountAddress::from_hex_literal("0x2").unwrap(); |
737 | | - let b_original_addr = AccountAddress::from_hex_literal("0x3").unwrap(); |
738 | | - let _a_original_addr = AccountAddress::from_hex_literal("0x4").unwrap(); |
739 | | -
|
740 | | - // publish c v0 |
741 | | - let packages = compile_modules_in_file("rt_c_v0.move", &[]); |
742 | | - assert!(packages.len() == 1); |
743 | | - let (runtime_package_id, modules) = packages.into_iter().next().unwrap(); |
744 | | - adapter |
745 | | - .publish_package_to_storage( |
746 | | - runtime_package_id, |
747 | | - runtime_package_id, |
748 | | - modules, |
749 | | - BTreeMap::new(), |
750 | | - BTreeSet::new(), |
751 | | - ) |
752 | | - .unwrap(); |
753 | | -
|
754 | | - assert_eq!(adapter.runtime()cache().package_cache().len(), 0); |
755 | | -
|
756 | | - // publish c v1 |
757 | | - let packages = compile_modules_in_file("rt_c_v1.move", &[]); |
758 | | - assert!(packages.len() == 1); |
759 | | - let (runtime_package_id, modules) = packages.into_iter().next().unwrap(); |
760 | | - adapter |
761 | | - .publish_package_to_storage( |
762 | | - runtime_package_id, |
763 | | - st_c_v1_addr, |
764 | | - modules, |
765 | | - [( |
766 | | - ( |
767 | | - ModuleId::new(runtime_package_id, Identifier::new("c").unwrap()), |
768 | | - Identifier::new("S").unwrap(), |
769 | | - ), |
770 | | - ModuleId::new(c_original_addr, Identifier::new("c").unwrap()), |
771 | | - )] |
772 | | - .into_iter() |
773 | | - .collect(), |
774 | | - BTreeSet::new(), |
775 | | - ) |
776 | | - .unwrap(); |
| 738 | +/// Compile a relinker test module with the given original/version IDs and dependencies. |
| 739 | +fn relinker_pkg( |
| 740 | + original_id: OriginalId, |
| 741 | + version_id: VersionId, |
| 742 | + module: &str, |
| 743 | + deps: &[&str], |
| 744 | +) -> StoredPackage { |
| 745 | + let base = make_base_path(); |
| 746 | + let source = base |
| 747 | + .join(format!("rt_{module}.move")) |
| 748 | + .to_string_lossy() |
| 749 | + .to_string(); |
| 750 | + let dep_paths: Vec<String> = deps |
| 751 | + .iter() |
| 752 | + .map(|d| base.join(format!("rt_{d}.move")).to_string_lossy().to_string()) |
| 753 | + .collect(); |
| 754 | + |
| 755 | + let (_, units) = Compiler::from_files( |
| 756 | + None, |
| 757 | + vec![source], |
| 758 | + dep_paths, |
| 759 | + BTreeMap::<String, _>::new(), |
| 760 | + ) |
| 761 | + .build_and_report() |
| 762 | + .expect("compilation failed"); |
777 | 763 |
|
778 | | - assert_eq!(adapter.cache.package_cache().len(), 1); |
| 764 | + let modules: Vec<CompiledModule> = expect_modules(units) |
| 765 | + .filter(|m| *m.self_id().address() == original_id) |
| 766 | + .collect(); |
| 767 | + StoredPackage::from_modules_for_testing(version_id, modules).unwrap() |
| 768 | +} |
779 | 769 |
|
780 | | - // publish b_v0 <- c_v0 |
781 | | - let packages = compile_modules_in_file("rt_b_v0.move", &["rt_c_v0.move"]); |
782 | | - assert!(packages.len() == 1); |
783 | | - let (runtime_package_id, modules) = packages.into_iter().next().unwrap(); |
784 | | - adapter |
785 | | - .publish_package_to_storage( |
786 | | - runtime_package_id, |
787 | | - runtime_package_id, |
788 | | - modules, |
789 | | - BTreeMap::new(), |
790 | | - [c_original_addr].into_iter().collect(), |
791 | | - ) |
792 | | - .unwrap(); |
| 770 | +// Test that publishing and relinking packages properly updates the cache: |
| 771 | +// - Different linkage contexts sharing a dependency reuse its cached Arc |
| 772 | +// - Different versions of the same original package (C v0 vs C v1) are cached separately |
| 773 | +// - A failed publish (linkage mismatch) does not corrupt the cache |
| 774 | +// |
| 775 | +// Package dependency structure: |
| 776 | +// C v0 (0x2): struct S, fun c() -> 42 |
| 777 | +// C v1 (0x5): struct S, struct R, fun c() -> 43, fun d() -> 44 |
| 778 | +// B v0 (0x3): fun b() = c::c() + 1 (depends on C) |
| 779 | +// A v0 (0x4): fun a() = b::b() + c::d() (depends on B and C v1) |
| 780 | + |
| 781 | +//Stores 4 packages with a relinking dependency chain: C v0 (0x2), C v1 (0x5, upgrade of C), B v0 (0x3, depends on C), A v0 (0x4, depends on B + C v1) |
| 782 | +//Loads B's dependency tree (B + C v0) into cache — verifies cache = 2 |
| 783 | +//Loads A's dependency tree (A + B + C v1) into cache — verifies cache = 4 and that B v0 is reused from cache (Arc::ptr_eq) rather than recompiled |
| 784 | +//Verifies C v0 and C v1 are separate cache entries (different VersionIds) |
| 785 | +//Attempts a bad publish of A linked against C v0 (A calls d() which only exists in C v1) — verifies it fails and cache is unchanged |
| 786 | +#[test] |
| 787 | +fn relink() { |
| 788 | + let c_orig = AccountAddress::from_hex_literal("0x2").unwrap(); |
| 789 | + let b_orig = AccountAddress::from_hex_literal("0x3").unwrap(); |
| 790 | + let a_orig = AccountAddress::from_hex_literal("0x4").unwrap(); |
| 791 | + let c_v1_addr = AccountAddress::from_hex_literal("0x5").unwrap(); |
793 | 792 |
|
794 | | - assert_eq!(adapter.cache.package_cache().len(), 2); |
| 793 | + let mut adapter = InMemoryTestAdapter::new(); |
795 | 794 |
|
796 | | - // publish b_v0 <- c_v1 |
797 | | - let packages = compile_modules_in_file("rt_b_v0.move", &["rt_c_v1.move"]); |
798 | | - assert!(packages.len() == 1); |
799 | | - let (runtime_package_id, modules) = packages.into_iter().next().unwrap(); |
800 | | - adapter |
801 | | - .publish_package_to_storage( |
802 | | - runtime_package_id, |
803 | | - st_b_v1_addr, |
804 | | - modules, |
805 | | - [ |
806 | | - ( |
807 | | - ( |
808 | | - ModuleId::new(c_original_addr, Identifier::new("c").unwrap()), |
809 | | - Identifier::new("S").unwrap(), |
810 | | - ), |
811 | | - ModuleId::new(c_original_addr, Identifier::new("c").unwrap()), |
812 | | - ), |
813 | | - ( |
814 | | - ( |
815 | | - ModuleId::new(st_c_v1_addr, Identifier::new("c").unwrap()), |
816 | | - Identifier::new("R").unwrap(), |
817 | | - ), |
818 | | - ModuleId::new(st_c_v1_addr, Identifier::new("c").unwrap()), |
819 | | - ), |
820 | | - ] |
821 | | - .into_iter() |
822 | | - .collect(), |
823 | | - [st_c_v1_addr].into_iter().collect(), |
824 | | - ) |
825 | | - .unwrap(); |
| 795 | + // -- Store all packages in storage -- |
| 796 | + |
| 797 | + // C v0 (original=0x2, stored at 0x2) |
| 798 | + let c0 = relinker_pkg(c_orig, c_orig, "c_v0", &[]); |
| 799 | + adapter.insert_package_into_storage(c0); |
| 800 | + |
| 801 | + // C v1 (original=0x2, stored at 0x5) with type origins: |
| 802 | + // S was defined in C v0 (0x2), R is new in C v1 (0x5) |
| 803 | + let mut c1 = relinker_pkg(c_orig, c_v1_addr, "c_v1", &[]); |
| 804 | + c1.0.type_origin_table = IndexMap::from([ |
| 805 | + ( |
| 806 | + IntraPackageName { |
| 807 | + module_name: Identifier::new("c").unwrap(), |
| 808 | + type_name: Identifier::new("S").unwrap(), |
| 809 | + }, |
| 810 | + c_orig, |
| 811 | + ), |
| 812 | + ( |
| 813 | + IntraPackageName { |
| 814 | + module_name: Identifier::new("c").unwrap(), |
| 815 | + type_name: Identifier::new("R").unwrap(), |
| 816 | + }, |
| 817 | + c_v1_addr, |
| 818 | + ), |
| 819 | + ]); |
| 820 | + adapter.insert_package_into_storage(c1); |
| 821 | + |
| 822 | + // B v0 (original=0x3, stored at 0x3) linked against C v0 |
| 823 | + let mut b0 = relinker_pkg(b_orig, b_orig, "b_v0", &["c_v0"]); |
| 824 | + b0.0.linkage_table = BTreeMap::from([(c_orig, c_orig), (b_orig, b_orig)]); |
| 825 | + adapter.insert_package_into_storage(b0); |
| 826 | + |
| 827 | + // A v0 (original=0x4, stored at 0x4) linked against C v1 and B v0 |
| 828 | + let mut a0 = relinker_pkg(a_orig, a_orig, "a_v0", &["b_v0", "c_v1"]); |
| 829 | + a0.0.linkage_table = |
| 830 | + BTreeMap::from([(c_orig, c_v1_addr), (b_orig, b_orig), (a_orig, a_orig)]); |
| 831 | + adapter.insert_package_into_storage(a0); |
| 832 | + |
| 833 | + // -- Load packages into cache with different linkage contexts -- |
| 834 | + |
| 835 | + assert_eq!(adapter.runtime().cache().package_cache().len(), 0); |
| 836 | + |
| 837 | + // Load B v0's dependency tree: B v0 + C v0 |
| 838 | + let link_b = LinkageContext::new(BTreeMap::from([(c_orig, c_orig), (b_orig, b_orig)])); |
| 839 | + load_linkage_packages_into_runtime(&mut adapter, &link_b).unwrap(); |
| 840 | + assert_eq!(adapter.runtime().cache().package_cache().len(), 2); |
826 | 841 |
|
827 | | - assert_eq!(adapter.cache.package_cache().len(), 4); |
| 842 | + // Capture B v0's cached Arc to verify reuse across linkage contexts |
| 843 | + let b_cached = adapter |
| 844 | + .runtime() |
| 845 | + .cache() |
| 846 | + .cached_package_at(b_orig) |
| 847 | + .expect("B v0 should be cached"); |
| 848 | + |
| 849 | + // Load A v0's dependency tree: A v0 + B v0 + C v1. |
| 850 | + // B v0 (at 0x3) should be reused from cache. |
| 851 | + // C v1 (at 0x5) is new — a different VersionId from C v0 (at 0x2). |
| 852 | + let link_a = LinkageContext::new(BTreeMap::from([ |
| 853 | + (c_orig, c_v1_addr), |
| 854 | + (b_orig, b_orig), |
| 855 | + (a_orig, a_orig), |
| 856 | + ])); |
| 857 | + load_linkage_packages_into_runtime(&mut adapter, &link_a).unwrap(); |
| 858 | + // Cache now has: C v0 (0x2), C v1 (0x5), B v0 (0x3), A v0 (0x4) |
| 859 | + assert_eq!(adapter.runtime().cache().package_cache().len(), 4); |
| 860 | + |
| 861 | + // B v0 is the same Arc (reused, not recompiled) |
| 862 | + let b_reused = adapter |
| 863 | + .runtime() |
| 864 | + .cache() |
| 865 | + .cached_package_at(b_orig) |
| 866 | + .expect("B v0 should still be cached"); |
| 867 | + assert!( |
| 868 | + Arc::ptr_eq(&b_cached, &b_reused), |
| 869 | + "B v0 should be reused across linkage contexts" |
| 870 | + ); |
828 | 871 |
|
829 | | - // publish a_v0 <- c_v1 && b_v0 |
830 | | - let packages = compile_modules_in_file("rt_a_v0.move", &["rt_c_v1.move", "rt_b_v0.move"]); |
831 | | - assert!(packages.len() == 1); |
832 | | - let (runtime_package_id, modules) = packages.into_iter().next().unwrap(); |
833 | | - adapter |
834 | | - .publish_package_to_storage( |
835 | | - runtime_package_id, |
836 | | - runtime_package_id, |
837 | | - modules, |
838 | | - [ |
839 | | - ( |
840 | | - ( |
841 | | - ModuleId::new(c_original_addr, Identifier::new("c").unwrap()), |
842 | | - Identifier::new("S").unwrap(), |
843 | | - ), |
844 | | - ModuleId::new(c_original_addr, Identifier::new("c").unwrap()), |
845 | | - ), |
846 | | - ( |
847 | | - ( |
848 | | - ModuleId::new(st_c_v1_addr, Identifier::new("c").unwrap()), |
849 | | - Identifier::new("R").unwrap(), |
850 | | - ), |
851 | | - ModuleId::new(st_c_v1_addr, Identifier::new("c").unwrap()), |
852 | | - ), |
853 | | - ] |
854 | | - .into_iter() |
855 | | - .collect(), |
856 | | - [st_c_v1_addr, b_original_addr].into_iter().collect(), |
857 | | - ) |
858 | | - .unwrap(); |
| 872 | + // Both C versions are cached as separate entries |
| 873 | + assert!(adapter.runtime().cache().cached_package_at(c_orig).is_some()); |
| 874 | + assert!( |
| 875 | + adapter |
| 876 | + .runtime() |
| 877 | + .cache() |
| 878 | + .cached_package_at(c_v1_addr) |
| 879 | + .is_some() |
| 880 | + ); |
859 | 881 |
|
860 | | - assert_eq!(adapter.cache.package_cache().len(), 5); |
| 882 | + let cache_before_bad = adapter.runtime().cache().package_cache().len(); |
861 | 883 |
|
862 | | - // publish a_v0 <- c_v0 && b_v0 -- ERROR since a_v0 requires c_v1+ |
863 | | - let packages = compile_modules_in_file("rt_a_v0.move", &["rt_c_v1.move", "rt_b_v0.move"]); |
864 | | - assert!(packages.len() == 1); |
865 | | - let (runtime_package_id, modules) = packages.into_iter().next().unwrap(); |
866 | | - adapter |
867 | | - .publish_package_to_storage( |
868 | | - runtime_package_id, |
869 | | - runtime_package_id, |
870 | | - modules, |
871 | | - [( |
872 | | - ( |
873 | | - ModuleId::new(c_original_addr, Identifier::new("c").unwrap()), |
874 | | - Identifier::new("S").unwrap(), |
875 | | - ), |
876 | | - ModuleId::new(c_original_addr, Identifier::new("c").unwrap()), |
877 | | - )] |
878 | | - .into_iter() |
879 | | - .collect(), |
880 | | - [c_original_addr, b_original_addr].into_iter().collect(), |
881 | | - ) |
882 | | - .unwrap_err(); |
| 884 | + // Try publishing A v0 linked against C v0 instead of C v1. |
| 885 | + // A calls d() which only exists in C v1, so verification should fail. |
| 886 | + let mut a0_bad = relinker_pkg(a_orig, a_orig, "a_v0", &["b_v0", "c_v1"]); |
| 887 | + a0_bad.0.linkage_table = |
| 888 | + BTreeMap::from([(c_orig, c_orig), (b_orig, b_orig), (a_orig, a_orig)]); |
| 889 | + let result = adapter.publish_package(a_orig, a0_bad.into_serialized_package()); |
| 890 | + assert!( |
| 891 | + result.is_err(), |
| 892 | + "A linked against C v0 should fail (missing d())" |
| 893 | + ); |
883 | 894 |
|
884 | | - // cache stays the same since the publish failed |
885 | | - assert_eq!(adapter.cache.package_cache().len(), 5); |
886 | | - */ |
| 895 | + // Cache must not change from a failed publish |
| 896 | + assert_eq!( |
| 897 | + adapter.runtime().cache().package_cache().len(), |
| 898 | + cache_before_bad, |
| 899 | + ); |
887 | 900 | } |
0 commit comments