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  }