Skip to content

Commit

Permalink
address Rati's comments
Browse files Browse the repository at this point in the history
  • Loading branch information
georgemitenkov committed Jan 11, 2025
1 parent 7eec28c commit a4b33f5
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 76 deletions.
59 changes: 37 additions & 22 deletions third_party/move/move-vm/runtime/src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1673,13 +1673,40 @@ pub const VALUE_DEPTH_MAX: u64 = 128;
const MAX_TYPE_TO_LAYOUT_NODES: u64 = 256;

pub(crate) struct PseudoGasContext {
pub(crate) max_cost: u64,
// Parameters for metering type tag construction:
// - maximum allowed cost,
// - base cost for any type to tag conversion,
// - cost for size of a struct tag.
max_cost: u64,
pub(crate) cost: u64,
pub(crate) cost_base: u64,
pub(crate) cost_per_byte: u64,
cost_base: u64,
cost_per_byte: u64,
}

impl PseudoGasContext {
pub(crate) fn new(vm_config: &VMConfig) -> Self {
Self {
max_cost: vm_config.type_max_cost,
cost: 0,
cost_base: vm_config.type_base_cost,
cost_per_byte: vm_config.type_byte_cost,
}
}

pub(crate) fn current_cost(&mut self) -> u64 {
self.cost
}

pub(crate) fn charge_base(&mut self) -> PartialVMResult<()> {
self.charge(self.cost_base)
}

pub(crate) fn charge_struct_tag(&mut self, struct_tag: &StructTag) -> PartialVMResult<()> {
let size =
(struct_tag.address.len() + struct_tag.module.len() + struct_tag.name.len()) as u64;
self.charge(size * self.cost_per_byte)
}

pub(crate) fn charge(&mut self, amount: u64) -> PartialVMResult<()> {
self.cost += amount;
if self.cost > self.max_cost {
Expand Down Expand Up @@ -1707,7 +1734,7 @@ impl LoaderV1 {
return Ok(struct_tag.clone());
}

let cur_cost = gas_context.cost;
let cur_cost = gas_context.current_cost();

let type_args = ty_args
.iter()
Expand All @@ -1717,14 +1744,12 @@ impl LoaderV1 {
.name_cache
.idx_to_struct_tag(struct_name_idx, type_args)?;

let size =
(struct_tag.address.len() + struct_tag.module.len() + struct_tag.name.len()) as u64;
gas_context.charge(size * gas_context.cost_per_byte)?;
gas_context.charge_struct_tag(&struct_tag)?;
self.type_cache.store_struct_tag(
struct_name_idx,
ty_args.to_vec(),
struct_tag.clone(),
gas_context.cost - cur_cost,
gas_context.current_cost() - cur_cost,
);
Ok(struct_tag)
}
Expand All @@ -1734,7 +1759,7 @@ impl LoaderV1 {
ty: &Type,
gas_context: &mut PseudoGasContext,
) -> PartialVMResult<TypeTag> {
gas_context.charge(gas_context.cost_base)?;
gas_context.charge_base()?;
Ok(match ty {
Type::Bool => TypeTag::Bool,
Type::U8 => TypeTag::U8,
Expand Down Expand Up @@ -2058,17 +2083,12 @@ impl Loader {
let count_before = *count;
let struct_tag = match self {
Loader::V1(loader) => {
let mut gas_context = PseudoGasContext {
cost: 0,
max_cost: loader.vm_config().type_max_cost,
cost_base: loader.vm_config().type_base_cost,
cost_per_byte: loader.vm_config().type_byte_cost,
};
let mut gas_context = PseudoGasContext::new(loader.vm_config());
loader.struct_name_to_type_tag(struct_name_idx, ty_args, &mut gas_context)?
},
Loader::V2(_) => {
let ty_tag_builder = TypeTagBuilder::new(module_storage.runtime_environment());
ty_tag_builder.struct_ty_to_struct_tag(&struct_name_idx, ty_args)?
ty_tag_builder.struct_name_idx_to_struct_tag(&struct_name_idx, ty_args)?
},
};
let fields = struct_type.fields(None)?;
Expand Down Expand Up @@ -2267,12 +2287,7 @@ impl Loader {
) -> PartialVMResult<TypeTag> {
match self {
Loader::V1(loader) => {
let mut gas_context = PseudoGasContext {
cost: 0,
max_cost: self.vm_config().type_max_cost,
cost_base: self.vm_config().type_base_cost,
cost_per_byte: self.vm_config().type_byte_cost,
};
let mut gas_context = PseudoGasContext::new(self.vm_config());
loader.type_to_type_tag_impl(ty, &mut gas_context)
},
Loader::V2(_) => {
Expand Down
87 changes: 33 additions & 54 deletions third_party/move/move-vm/runtime/src/storage/ty_tag_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub(crate) struct PricedStructTag {
/// A struct name corresponds to a unique [StructNameIndex]. So all non-generic structs with same
/// names have the same struct tags. If structs are generic, the number of type parameters cannot
/// be changed by the upgrade, so the tags stay the same for different "upgraded" struct versions.
/// The type parameters themselves (vector of [Type]s in this cache used as keys) are also not
/// changing.
///
/// Note: even if we allow to add more type parameters (e.g., for enums), it still does not affect
/// safety because different number of type parameters will correspond to a different entries in
Expand Down Expand Up @@ -100,57 +102,35 @@ impl TypeTagCache {
/// Responsible for building type tags, while also doing the metering in order to bound space and
/// time complexity.
pub(crate) struct TypeTagBuilder<'a> {
// Parameters for metering type tag construction:
// - maximum allowed cost,
// - base cost for any type to tag conversion,
// - cost for size of a struct tag.
max_cost: u64,
cost_base: u64,
cost_per_byte: u64,

// Stores caches for struct names and tags.
/// Stores caches for struct names and tags, as well as pseudo-gas metering configs.
runtime_environment: &'a RuntimeEnvironment,
}

impl<'a> TypeTagBuilder<'a> {
/// Creates a new builder for the specified environment and configs.
pub(crate) fn new(runtime_environment: &'a RuntimeEnvironment) -> Self {
let vm_config = runtime_environment.vm_config();
Self {
max_cost: vm_config.type_max_cost,
cost_base: vm_config.type_base_cost,
cost_per_byte: vm_config.type_byte_cost,
runtime_environment,
}
}

/// Converts a runtime type into a type tag. If the type is too complex (e.g., struct name size
/// too large, or type too deeply nested), an error is returned.
pub(crate) fn ty_to_ty_tag(&self, ty: &Type) -> PartialVMResult<TypeTag> {
let mut gas_context = PseudoGasContext {
cost: 0,
max_cost: self.max_cost,
cost_base: self.cost_base,
cost_per_byte: self.cost_per_byte,
};
let mut gas_context = PseudoGasContext::new(self.runtime_environment.vm_config());
self.ty_to_ty_tag_impl(ty, &mut gas_context)
}

/// Converts the struct type (based on its indexed name and type arguments) into a struct tag.
/// If the tag has not been previously cached, it will be cached. Just like for types, if the
/// type arguments are too complex, etc. the tag construction fails.
pub(crate) fn struct_ty_to_struct_tag(
pub(crate) fn struct_name_idx_to_struct_tag(
&self,
struct_name_idx: &StructNameIndex,
ty_args: &[Type],
) -> PartialVMResult<StructTag> {
let mut gas_context = PseudoGasContext {
cost: 0,
max_cost: self.max_cost,
cost_base: self.cost_base,
cost_per_byte: self.cost_per_byte,
};
self.struct_name_to_ty_tag(struct_name_idx, ty_args, &mut gas_context)
let mut gas_context = PseudoGasContext::new(self.runtime_environment.vm_config());
self.struct_name_idx_to_struct_tag_impl(struct_name_idx, ty_args, &mut gas_context)
}

fn ty_to_ty_tag_impl(
Expand All @@ -159,7 +139,7 @@ impl<'a> TypeTagBuilder<'a> {
gas_context: &mut PseudoGasContext,
) -> PartialVMResult<TypeTag> {
// Charge base cost at the start.
gas_context.charge(gas_context.cost_base)?;
gas_context.charge_base()?;

Ok(match ty {
// Primitive types.
Expand All @@ -181,11 +161,12 @@ impl<'a> TypeTagBuilder<'a> {

// Structs: we need to convert indices to names, possibly caching struct tags.
Type::Struct { idx, .. } => {
let struct_tag = self.struct_name_to_ty_tag(idx, &[], gas_context)?;
let struct_tag = self.struct_name_idx_to_struct_tag_impl(idx, &[], gas_context)?;
TypeTag::Struct(Box::new(struct_tag))
},
Type::StructInstantiation { idx, ty_args, .. } => {
let struct_tag = self.struct_name_to_ty_tag(idx, ty_args, gas_context)?;
let struct_tag =
self.struct_name_idx_to_struct_tag_impl(idx, ty_args, gas_context)?;
TypeTag::Struct(Box::new(struct_tag))
},

Expand All @@ -199,7 +180,7 @@ impl<'a> TypeTagBuilder<'a> {
})
}

fn struct_name_to_ty_tag(
fn struct_name_idx_to_struct_tag_impl(
&self,
struct_name_idx: &StructNameIndex,
ty_args: &[Type],
Expand All @@ -214,7 +195,7 @@ impl<'a> TypeTagBuilder<'a> {
}

// If not cached, record the current cost and construct tags for type arguments.
let cur_cost = gas_context.cost;
let cur_cost = gas_context.current_cost();

let type_args = ty_args
.iter()
Expand All @@ -224,16 +205,12 @@ impl<'a> TypeTagBuilder<'a> {
// Construct the struct tag as well.
let struct_name_index_map = self.runtime_environment.struct_name_index_map();
let struct_tag = struct_name_index_map.idx_to_struct_tag(*struct_name_idx, type_args)?;

// Calculate and charge the cost for the struct tag, proportionally to its size.
let size =
(struct_tag.address.len() + struct_tag.module.len() + struct_tag.name.len()) as u64;
gas_context.charge(size * gas_context.cost_per_byte)?;
gas_context.charge_struct_tag(&struct_tag)?;

// Cache the struct tag. Record its gas cost as well.
let priced_tag = PricedStructTag {
struct_tag,
pseudo_gas_cost: gas_context.cost - cur_cost,
pseudo_gas_cost: gas_context.current_cost() - cur_cost,
};
ty_tag_cache.insert_struct_tag(struct_name_idx, ty_args, &priced_tag);

Expand Down Expand Up @@ -405,10 +382,11 @@ mod tests {

#[test]
fn test_ty_to_ty_tag_struct_metering() {
let type_max_cost = 75;
let vm_config = VMConfig {
type_base_cost: 1,
type_byte_cost: 2,
type_max_cost: 76,
type_max_cost,
..Default::default()
};
let runtime_environment = RuntimeEnvironment::new_with_config(vec![], vm_config);
Expand All @@ -424,35 +402,36 @@ mod tests {
.unwrap();
let struct_tag = StructTag::from_str("0x1::foo::Foo").unwrap();

let mut gas_context = PseudoGasContext {
cost: 0,
max_cost: ty_tag_builder.max_cost,
cost_base: ty_tag_builder.cost_base,
cost_per_byte: ty_tag_builder.cost_per_byte,
};
let mut gas_context = PseudoGasContext::new(runtime_environment.vm_config());
assert_ok_eq!(
ty_tag_builder.struct_name_to_ty_tag(&idx, &[], &mut gas_context),
ty_tag_builder.struct_name_idx_to_struct_tag_impl(&idx, &[], &mut gas_context),
struct_tag.clone()
);

// Address size, plus module name and struct name each taking 3 characters.
let expected_cost = 2 * (32 + 3 + 3);
assert_eq!(gas_context.cost, expected_cost);
assert_eq!(gas_context.current_cost(), expected_cost);

let priced_tag = assert_some!(runtime_environment.ty_tag_cache().get_struct_tag(&idx, &[]));
assert_eq!(priced_tag.pseudo_gas_cost, expected_cost);
assert_eq!(priced_tag.struct_tag, struct_tag);

runtime_environment.ty_tag_cache().flush();
let mut gas_context = PseudoGasContext {
cost: 0,
// Now
let vm_config = VMConfig {
type_base_cost: 1,
type_byte_cost: 2,
// Use smaller limit, to test metering.
max_cost: ty_tag_builder.max_cost - 1,
cost_base: ty_tag_builder.cost_base,
cost_per_byte: ty_tag_builder.cost_per_byte,
type_max_cost: type_max_cost - 1,
..Default::default()
};
let runtime_environment = RuntimeEnvironment::new_with_config(vec![], vm_config);
let mut gas_context = PseudoGasContext::new(runtime_environment.vm_config());

let err = assert_err!(ty_tag_builder.struct_name_to_ty_tag(&idx, &[], &mut gas_context));
let err = assert_err!(ty_tag_builder.struct_name_idx_to_struct_tag_impl(
&idx,
&[],
&mut gas_context
));
assert_eq!(err.major_status(), StatusCode::TYPE_TAG_LIMIT_EXCEEDED);
assert_none!(runtime_environment.ty_tag_cache().get_struct_tag(&idx, &[]));
}
Expand Down

0 comments on commit a4b33f5

Please sign in to comment.