Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize storage migration: Allow skipping of values #3157

Merged
merged 7 commits into from
Mar 8, 2024
55 changes: 55 additions & 0 deletions migrations/capcons/capabilitymigration.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,58 @@ func (m *CapabilityValueMigration) Migrate(

return nil, nil
}

func (m *CapabilityValueMigration) CanSkip(
_ interpreter.StorageKey,
_ interpreter.StorageMapKey,
value interpreter.Value,
interpreter *interpreter.Interpreter,
) bool {
return CanSkipCapabilityValueMigration(value.StaticType(interpreter))
}

func CanSkipCapabilityValueMigration(valueType interpreter.StaticType) bool {
switch valueType := valueType.(type) {
case *interpreter.DictionaryStaticType:
return CanSkipCapabilityValueMigration(valueType.KeyType) &&
CanSkipCapabilityValueMigration(valueType.ValueType)

case interpreter.ArrayStaticType:
return CanSkipCapabilityValueMigration(valueType.ElementType())

case *interpreter.OptionalStaticType:
return CanSkipCapabilityValueMigration(valueType.Type)

case *interpreter.CapabilityStaticType:
return false

case interpreter.PrimitiveStaticType:

switch valueType {
case interpreter.PrimitiveStaticTypeCapability:
return false

case interpreter.PrimitiveStaticTypeBool,
interpreter.PrimitiveStaticTypeVoid,
interpreter.PrimitiveStaticTypeAddress,
interpreter.PrimitiveStaticTypeMetaType,
interpreter.PrimitiveStaticTypeBlock,
interpreter.PrimitiveStaticTypeString,
interpreter.PrimitiveStaticTypeCharacter:

return true
}

if !valueType.IsDeprecated() { //nolint:staticcheck
semaType := valueType.SemaType()

if sema.IsSubType(semaType, sema.NumberType) ||
sema.IsSubType(semaType, sema.PathType) {

return true
}
}
}

return false
}
10 changes: 10 additions & 0 deletions migrations/capcons/linkmigration.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,16 @@ func (*LinkValueMigration) Name() string {
return "LinkValueMigration"
}

func (m *LinkValueMigration) CanSkip(
_ interpreter.StorageKey,
_ interpreter.StorageMapKey,
value interpreter.Value,
interpreter *interpreter.Interpreter,
) bool {
// Link values have a capability static type
return CanSkipCapabilityValueMigration(value.StaticType(interpreter))
}

func (m *LinkValueMigration) Migrate(
storageKey interpreter.StorageKey,
storageMapKey interpreter.StorageMapKey,
Expand Down
113 changes: 113 additions & 0 deletions migrations/capcons/migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2349,3 +2349,116 @@ func TestUntypedPathCapabilityValueMigration(t *testing.T) {
require.NoError(t, err)

}

func TestCanSkipCapabilityValueMigration(t *testing.T) {

t.Parallel()

testCases := map[interpreter.StaticType]bool{

// Primitive types, like Bool and Address

interpreter.PrimitiveStaticTypeBool: true,
interpreter.PrimitiveStaticTypeAddress: true,

// Number and Path types, like UInt8 and StoragePath

interpreter.PrimitiveStaticTypeUInt8: true,
interpreter.PrimitiveStaticTypeStoragePath: true,

// Capability types

interpreter.PrimitiveStaticTypeCapability: false,
&interpreter.CapabilityStaticType{
BorrowType: interpreter.PrimitiveStaticTypeString,
}: false,
&interpreter.CapabilityStaticType{
BorrowType: interpreter.PrimitiveStaticTypeCharacter,
}: false,

// Existential types, like AnyStruct and AnyResource

interpreter.PrimitiveStaticTypeAnyStruct: false,
interpreter.PrimitiveStaticTypeAnyResource: false,
}

test := func(ty interpreter.StaticType, expected bool) {

t.Run(ty.String(), func(t *testing.T) {

t.Parallel()

t.Run("base", func(t *testing.T) {

t.Parallel()

actual := CanSkipCapabilityValueMigration(ty)
assert.Equal(t, expected, actual)

})

t.Run("optional", func(t *testing.T) {

t.Parallel()

optionalType := interpreter.NewOptionalStaticType(nil, ty)

actual := CanSkipCapabilityValueMigration(optionalType)
assert.Equal(t, expected, actual)
})

t.Run("variable-sized", func(t *testing.T) {

t.Parallel()

arrayType := interpreter.NewVariableSizedStaticType(nil, ty)

actual := CanSkipCapabilityValueMigration(arrayType)
assert.Equal(t, expected, actual)
})

t.Run("constant-sized", func(t *testing.T) {

t.Parallel()

arrayType := interpreter.NewConstantSizedStaticType(nil, ty, 2)

actual := CanSkipCapabilityValueMigration(arrayType)
assert.Equal(t, expected, actual)
})

t.Run("dictionary key", func(t *testing.T) {

t.Parallel()

dictionaryType := interpreter.NewDictionaryStaticType(
nil,
ty,
interpreter.PrimitiveStaticTypeInt,
)

actual := CanSkipCapabilityValueMigration(dictionaryType)
assert.Equal(t, expected, actual)

})

t.Run("dictionary value", func(t *testing.T) {

t.Parallel()

dictionaryType := interpreter.NewDictionaryStaticType(
nil,
interpreter.PrimitiveStaticTypeInt,
ty,
)

actual := CanSkipCapabilityValueMigration(dictionaryType)
assert.Equal(t, expected, actual)
})
})
}

for ty, expected := range testCases {
test(ty, expected)
}
}
9 changes: 9 additions & 0 deletions migrations/entitlements/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,12 @@ func (mig EntitlementsMigration) Migrate(
) {
return ConvertValueToEntitlements(mig.Interpreter, value)
}

func (mig EntitlementsMigration) CanSkip(
_ interpreter.StorageKey,
_ interpreter.StorageMapKey,
value interpreter.Value,
interpreter *interpreter.Interpreter,
) bool {
return statictypes.CanSkipStaticTypeMigration(value.StaticType(interpreter))
}
9 changes: 9 additions & 0 deletions migrations/entitlements/migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,15 @@ func (m testEntitlementsMigration) Migrate(
return ConvertValueToEntitlements(m.inter, value)
}

func (m testEntitlementsMigration) CanSkip(
_ interpreter.StorageKey,
_ interpreter.StorageMapKey,
_ interpreter.Value,
_ *interpreter.Interpreter,
) bool {
return false
}

func convertEntireTestValue(
t *testing.T,
inter *interpreter.Interpreter,
Expand Down
21 changes: 21 additions & 0 deletions migrations/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ type ValueMigration interface {
value interpreter.Value,
interpreter *interpreter.Interpreter,
) (newValue interpreter.Value, err error)
CanSkip(
storageKey interpreter.StorageKey,
storageMapKey interpreter.StorageMapKey,
value interpreter.Value,
interpreter *interpreter.Interpreter,
) bool
}

type DomainMigration interface {
Expand Down Expand Up @@ -170,6 +176,21 @@ func (m *StorageMigration) MigrateNestedValue(
}
}()

// skip the migration of the value,
// if all value migrations agree

canSkip := true
for _, migration := range valueMigrations {
if !migration.CanSkip(storageKey, storageMapKey, value, m.interpreter) {
canSkip = false
break
}
}

if canSkip {
return
}

// Visit the children first, and migrate them.
// i.e: depth-first traversal
switch typedValue := value.(type) {
Expand Down
Loading
Loading