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
Merged
50 changes: 50 additions & 0 deletions migrations/capcons/capabilitymigration.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,53 @@ func (m *CapabilityValueMigration) Migrate(

return nil, nil
}

func (m *CapabilityValueMigration) CanSkip(valueType interpreter.StaticType) bool {
return CanSkipCapabilityValueMigration(valueType)
}

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
}
5 changes: 5 additions & 0 deletions migrations/capcons/linkmigration.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ func (*LinkValueMigration) Name() string {
return "LinkValueMigration"
}

func (m *LinkValueMigration) CanSkip(valueType interpreter.StaticType) bool {
// Link values have a capability static type
return CanSkipCapabilityValueMigration(valueType)
}

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)
}
}
4 changes: 4 additions & 0 deletions migrations/entitlements/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,7 @@ func (mig EntitlementsMigration) Migrate(
) {
return ConvertValueToEntitlements(mig.Interpreter, value)
}

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

func (m testEntitlementsMigration) CanSkip(_ interpreter.StaticType) bool {
return false
}

func convertEntireTestValue(
t *testing.T,
inter *interpreter.Interpreter,
Expand Down
51 changes: 35 additions & 16 deletions migrations/migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type ValueMigration interface {
value interpreter.Value,
interpreter *interpreter.Interpreter,
) (newValue interpreter.Value, err error)
CanSkip(valueType interpreter.StaticType) bool
}

type DomainMigration interface {
Expand Down Expand Up @@ -170,11 +171,29 @@ func (m *StorageMigration) MigrateNestedValue(
}
}()

inter := m.interpreter

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

canSkip := true
staticType := value.StaticType(inter)
for _, migration := range valueMigrations {
if !migration.CanSkip(staticType) {
canSkip = false
break
}
}

if canSkip {
return
}

// Visit the children first, and migrate them.
// i.e: depth-first traversal
switch typedValue := value.(type) {
case *interpreter.SomeValue:
innerValue := typedValue.InnerValue(m.interpreter, emptyLocationRange)
innerValue := typedValue.InnerValue(inter, emptyLocationRange)
newInnerValue := m.MigrateNestedValue(
storageKey,
storageMapKey,
Expand All @@ -183,7 +202,7 @@ func (m *StorageMigration) MigrateNestedValue(
reporter,
)
if newInnerValue != nil {
migratedValue = interpreter.NewSomeValueNonCopying(m.interpreter, newInnerValue)
migratedValue = interpreter.NewSomeValueNonCopying(inter, newInnerValue)

// chain the migrations
value = migratedValue
Expand All @@ -196,7 +215,7 @@ func (m *StorageMigration) MigrateNestedValue(
count := array.Count()
for index := 0; index < count; index++ {

element := array.Get(m.interpreter, emptyLocationRange, index)
element := array.Get(inter, emptyLocationRange, index)

newElement := m.MigrateNestedValue(
storageKey,
Expand All @@ -211,17 +230,17 @@ func (m *StorageMigration) MigrateNestedValue(
}

existingStorable := array.RemoveWithoutTransfer(
m.interpreter,
inter,
emptyLocationRange,
index,
)

interpreter.StoredValue(m.interpreter, existingStorable, m.storage).
DeepRemove(m.interpreter)
m.interpreter.RemoveReferencedSlab(existingStorable)
interpreter.StoredValue(inter, existingStorable, m.storage).
DeepRemove(inter)
inter.RemoveReferencedSlab(existingStorable)

array.InsertWithoutTransfer(
m.interpreter,
inter,
emptyLocationRange,
index,
newElement,
Expand All @@ -241,7 +260,7 @@ func (m *StorageMigration) MigrateNestedValue(

for _, fieldName := range fieldNames {
existingValue := composite.GetField(
m.interpreter,
inter,
emptyLocationRange,
fieldName,
)
Expand All @@ -259,7 +278,7 @@ func (m *StorageMigration) MigrateNestedValue(
}

composite.SetMemberWithoutTransfer(
m.interpreter,
inter,
emptyLocationRange,
fieldName,
migratedValue,
Expand Down Expand Up @@ -329,7 +348,7 @@ func (m *StorageMigration) MigrateNestedValue(

existingKey = legacyKey(existingKey)
existingKeyStorable, existingValueStorable := dictionary.RemoveWithoutTransfer(
m.interpreter,
inter,
emptyLocationRange,
existingKey,
)
Expand All @@ -347,13 +366,13 @@ func (m *StorageMigration) MigrateNestedValue(
// Value was migrated
valueToSet = newValue

interpreter.StoredValue(m.interpreter, existingValueStorable, m.storage).
DeepRemove(m.interpreter)
m.interpreter.RemoveReferencedSlab(existingValueStorable)
interpreter.StoredValue(inter, existingValueStorable, m.storage).
DeepRemove(inter)
inter.RemoveReferencedSlab(existingValueStorable)
}

dictionary.InsertWithoutTransfer(
m.interpreter,
inter,
emptyLocationRange,
keyToSet,
valueToSet,
Expand All @@ -372,7 +391,7 @@ func (m *StorageMigration) MigrateNestedValue(
if newInnerValue != nil {
newInnerCapability := newInnerValue.(*interpreter.IDCapabilityValue)
migratedValue = interpreter.NewPublishedValue(
m.interpreter,
inter,
publishedValue.Recipient,
newInnerCapability,
)
Expand Down
Loading
Loading