github.com/onflow/flow-go@v0.33.17/cmd/util/ledger/migrations/deduplicate_contract_names_migration_test.go (about) 1 package migrations_test 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "sort" 8 "testing" 9 10 "github.com/fxamacker/cbor/v2" 11 "github.com/rs/zerolog" 12 "github.com/stretchr/testify/require" 13 14 "github.com/onflow/cadence/runtime/common" 15 16 "github.com/onflow/flow-go/cmd/util/ledger/migrations" 17 "github.com/onflow/flow-go/fvm/environment" 18 "github.com/onflow/flow-go/ledger" 19 "github.com/onflow/flow-go/ledger/common/convert" 20 "github.com/onflow/flow-go/model/flow" 21 ) 22 23 func TestDeduplicateContractNamesMigration(t *testing.T) { 24 migration := migrations.DeduplicateContractNamesMigration{} 25 log := zerolog.New(zerolog.NewTestWriter(t)) 26 err := migration.InitMigration(log, nil, 0) 27 require.NoError(t, err) 28 29 address, err := common.HexToAddress("0x1") 30 require.NoError(t, err) 31 32 ctx := context.Background() 33 34 accountStatus := environment.NewAccountStatus() 35 accountStatus.SetStorageUsed(1000) 36 accountStatusPayload := ledger.NewPayload( 37 convert.RegisterIDToLedgerKey( 38 flow.AccountStatusRegisterID(flow.ConvertAddress(address)), 39 ), 40 accountStatus.ToBytes(), 41 ) 42 43 contractNamesPayload := func(contractNames []byte) *ledger.Payload { 44 return ledger.NewPayload( 45 convert.RegisterIDToLedgerKey( 46 flow.RegisterID{ 47 Owner: string(address.Bytes()), 48 Key: flow.ContractNamesKey, 49 }, 50 ), 51 contractNames, 52 ) 53 } 54 55 requireContractNames := func(payloads []*ledger.Payload, f func([]string)) { 56 for _, payload := range payloads { 57 key, err := payload.Key() 58 require.NoError(t, err) 59 id, err := convert.LedgerKeyToRegisterID(key) 60 require.NoError(t, err) 61 62 if id.Key != flow.ContractNamesKey { 63 continue 64 } 65 66 contracts := make([]string, 0) 67 err = cbor.Unmarshal(payload.Value(), &contracts) 68 require.NoError(t, err) 69 70 f(contracts) 71 72 } 73 } 74 75 t.Run("no contract names", func(t *testing.T) { 76 payloads, err := migration.MigrateAccount(ctx, address, 77 []*ledger.Payload{ 78 accountStatusPayload, 79 }, 80 ) 81 82 require.NoError(t, err) 83 require.Equal(t, 1, len(payloads)) 84 }) 85 86 t.Run("one contract", func(t *testing.T) { 87 contractNames := []string{"test"} 88 newContractNames, err := cbor.Marshal(contractNames) 89 require.NoError(t, err) 90 91 payloads, err := migration.MigrateAccount(ctx, address, 92 []*ledger.Payload{ 93 accountStatusPayload, 94 contractNamesPayload(newContractNames), 95 }, 96 ) 97 98 require.NoError(t, err) 99 require.Equal(t, 2, len(payloads)) 100 101 requireContractNames(payloads, func(contracts []string) { 102 require.Equal(t, 1, len(contracts)) 103 require.Equal(t, "test", contracts[0]) 104 }) 105 }) 106 107 t.Run("two unique contracts", func(t *testing.T) { 108 contractNames := []string{"test", "test2"} 109 newContractNames, err := cbor.Marshal(contractNames) 110 require.NoError(t, err) 111 112 payloads, err := migration.MigrateAccount(ctx, address, 113 []*ledger.Payload{ 114 accountStatusPayload, 115 contractNamesPayload(newContractNames), 116 }, 117 ) 118 119 require.NoError(t, err) 120 require.Equal(t, 2, len(payloads)) 121 122 requireContractNames(payloads, func(contracts []string) { 123 require.Equal(t, 2, len(contracts)) 124 require.Equal(t, "test", contracts[0]) 125 require.Equal(t, "test2", contracts[1]) 126 }) 127 }) 128 129 t.Run("two contracts", func(t *testing.T) { 130 contractNames := []string{"test", "test"} 131 newContractNames, err := cbor.Marshal(contractNames) 132 require.NoError(t, err) 133 134 payloads, err := migration.MigrateAccount(ctx, address, 135 []*ledger.Payload{ 136 accountStatusPayload, 137 contractNamesPayload(newContractNames), 138 }, 139 ) 140 141 require.NoError(t, err) 142 require.Equal(t, 2, len(payloads)) 143 144 requireContractNames(payloads, func(contracts []string) { 145 require.Equal(t, 1, len(contracts)) 146 require.Equal(t, "test", contracts[0]) 147 }) 148 }) 149 150 t.Run("not sorted contracts", func(t *testing.T) { 151 contractNames := []string{"test2", "test"} 152 newContractNames, err := cbor.Marshal(contractNames) 153 require.NoError(t, err) 154 155 _, err = migration.MigrateAccount(ctx, address, 156 []*ledger.Payload{ 157 accountStatusPayload, 158 contractNamesPayload(newContractNames), 159 }, 160 ) 161 162 require.Error(t, err) 163 }) 164 165 t.Run("duplicate contracts", func(t *testing.T) { 166 contractNames := []string{"test", "test", "test2", "test3", "test3"} 167 newContractNames, err := cbor.Marshal(contractNames) 168 require.NoError(t, err) 169 170 payloads, err := migration.MigrateAccount(ctx, address, 171 []*ledger.Payload{ 172 accountStatusPayload, 173 contractNamesPayload(newContractNames), 174 }, 175 ) 176 177 require.NoError(t, err) 178 require.Equal(t, 2, len(payloads)) 179 180 requireContractNames(payloads, func(contracts []string) { 181 require.Equal(t, 3, len(contracts)) 182 require.Equal(t, "test", contracts[0]) 183 require.Equal(t, "test2", contracts[1]) 184 require.Equal(t, "test3", contracts[2]) 185 }) 186 }) 187 188 t.Run("random contracts", func(t *testing.T) { 189 contractNames := make([]string, 1000) 190 uniqueContracts := 1 191 for i := 0; i < 1000; i++ { 192 // i > 0 so it's easier to know how many unique contracts there are 193 if i > 0 && rand.Float32() < 0.5 { 194 uniqueContracts++ 195 } 196 contractNames[i] = fmt.Sprintf("test%d", uniqueContracts) 197 } 198 199 // sort contractNames alphabetically, because they are not sorted 200 sort.Slice(contractNames, func(i, j int) bool { 201 return contractNames[i] < contractNames[j] 202 }) 203 204 newContractNames, err := cbor.Marshal(contractNames) 205 require.NoError(t, err) 206 207 payloads, err := migration.MigrateAccount(ctx, address, 208 []*ledger.Payload{ 209 accountStatusPayload, 210 contractNamesPayload(newContractNames), 211 }, 212 ) 213 214 require.NoError(t, err) 215 require.Equal(t, 2, len(payloads)) 216 217 requireContractNames(payloads, func(contracts []string) { 218 require.Equal(t, uniqueContracts, len(contracts)) 219 }) 220 }) 221 }