github.com/onflow/flow-go@v0.33.17/cmd/util/ledger/migrations/deduplicate_contract_names_migration.go (about)

     1  package migrations
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/fxamacker/cbor/v2"
     8  	"github.com/rs/zerolog"
     9  
    10  	"github.com/onflow/cadence/runtime/common"
    11  
    12  	"github.com/onflow/flow-go/ledger"
    13  	"github.com/onflow/flow-go/ledger/common/convert"
    14  	"github.com/onflow/flow-go/model/flow"
    15  )
    16  
    17  // DeduplicateContractNamesMigration checks if the contract names have been duplicated and
    18  // removes the duplicate ones.
    19  //
    20  // This migration de-syncs storage used, so it should be run before the StorageUsedMigration.
    21  type DeduplicateContractNamesMigration struct {
    22  	log zerolog.Logger
    23  }
    24  
    25  func (d *DeduplicateContractNamesMigration) Close() error {
    26  	return nil
    27  }
    28  
    29  func (d *DeduplicateContractNamesMigration) InitMigration(
    30  	log zerolog.Logger,
    31  	_ []*ledger.Payload,
    32  	_ int,
    33  ) error {
    34  	d.log = log.
    35  		With().
    36  		Str("migration", "DeduplicateContractNamesMigration").
    37  		Logger()
    38  
    39  	return nil
    40  }
    41  
    42  func (d *DeduplicateContractNamesMigration) MigrateAccount(
    43  	ctx context.Context,
    44  	address common.Address,
    45  	payloads []*ledger.Payload,
    46  ) ([]*ledger.Payload, error) {
    47  	flowAddress := flow.ConvertAddress(address)
    48  	contractNamesID := flow.ContractNamesRegisterID(flowAddress)
    49  
    50  	var contractNamesPayload *ledger.Payload
    51  	contractNamesPayloadIndex := 0
    52  	for i, payload := range payloads {
    53  		key, err := payload.Key()
    54  		if err != nil {
    55  			return nil, err
    56  		}
    57  		id, err := convert.LedgerKeyToRegisterID(key)
    58  		if err != nil {
    59  			return nil, err
    60  		}
    61  		if id == contractNamesID {
    62  			contractNamesPayload = payload
    63  			contractNamesPayloadIndex = i
    64  			break
    65  		}
    66  	}
    67  	if contractNamesPayload == nil {
    68  		return payloads, nil
    69  	}
    70  
    71  	value := contractNamesPayload.Value()
    72  	if len(value) == 0 {
    73  		// Remove the empty payload
    74  		copy(payloads[contractNamesPayloadIndex:], payloads[contractNamesPayloadIndex+1:])
    75  		payloads = payloads[:len(payloads)-1]
    76  
    77  		return payloads, nil
    78  	}
    79  
    80  	var contractNames []string
    81  	err := cbor.Unmarshal(value, &contractNames)
    82  	if err != nil {
    83  		return nil, fmt.Errorf("failed to get contract names: %w", err)
    84  	}
    85  
    86  	var foundDuplicate bool
    87  	i := 1
    88  	for i < len(contractNames) {
    89  		if contractNames[i-1] != contractNames[i] {
    90  
    91  			if contractNames[i-1] > contractNames[i] {
    92  				// this is not a valid state and we should fail.
    93  				// Contract names must be sorted by definition.
    94  				return nil, fmt.Errorf(
    95  					"contract names for account %s are not sorted: %s",
    96  					address.Hex(),
    97  					contractNames,
    98  				)
    99  			}
   100  
   101  			i++
   102  			continue
   103  		}
   104  		// Found duplicate (contactNames[i-1] == contactNames[i])
   105  		// Remove contractNames[i]
   106  		copy(contractNames[i:], contractNames[i+1:])
   107  		contractNames = contractNames[:len(contractNames)-1]
   108  		foundDuplicate = true
   109  	}
   110  
   111  	if !foundDuplicate {
   112  		return payloads, nil
   113  	}
   114  
   115  	d.log.Info().
   116  		Str("address", address.Hex()).
   117  		Strs("contract_names", contractNames).
   118  		Msg("removing duplicate contract names")
   119  
   120  	newContractNames, err := cbor.Marshal(contractNames)
   121  	if err != nil {
   122  		return nil, fmt.Errorf(
   123  			"cannot encode contract names: %s",
   124  			contractNames,
   125  		)
   126  	}
   127  
   128  	payloads[contractNamesPayloadIndex] = ledger.NewPayload(convert.RegisterIDToLedgerKey(contractNamesID), newContractNames)
   129  	return payloads, nil
   130  
   131  }
   132  
   133  var _ AccountBasedMigration = &DeduplicateContractNamesMigration{}