github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/cmd/util/ledger/migrations/fix_broken_data_migration.go (about)

     1  package migrations
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/onflow/cadence/migrations"
    11  	"github.com/rs/zerolog"
    12  
    13  	"github.com/onflow/atree"
    14  
    15  	"github.com/onflow/cadence/runtime/common"
    16  
    17  	"github.com/onflow/flow-go/cmd/util/ledger/reporters"
    18  	"github.com/onflow/flow-go/cmd/util/ledger/util"
    19  	"github.com/onflow/flow-go/cmd/util/ledger/util/registers"
    20  	"github.com/onflow/flow-go/ledger"
    21  	"github.com/onflow/flow-go/ledger/common/convert"
    22  	"github.com/onflow/flow-go/model/flow"
    23  )
    24  
    25  type FixSlabsWithBrokenReferencesMigration struct {
    26  	log            zerolog.Logger
    27  	rw             reporters.ReportWriter
    28  	outputDir      string
    29  	accountsToFix  map[common.Address]struct{}
    30  	nWorkers       int
    31  	mutex          sync.Mutex
    32  	brokenPayloads []*ledger.Payload
    33  	payloadsFile   string
    34  }
    35  
    36  var _ AccountBasedMigration = &FixSlabsWithBrokenReferencesMigration{}
    37  
    38  const fixSlabsWithBrokenReferencesName = "fix-slabs-with-broken-references"
    39  
    40  func NewFixBrokenReferencesInSlabsMigration(
    41  	outputDir string,
    42  	rwf reporters.ReportWriterFactory,
    43  	accountsToFix map[common.Address]struct{},
    44  ) *FixSlabsWithBrokenReferencesMigration {
    45  	return &FixSlabsWithBrokenReferencesMigration{
    46  		outputDir:      outputDir,
    47  		rw:             rwf.ReportWriter(fixSlabsWithBrokenReferencesName),
    48  		accountsToFix:  accountsToFix,
    49  		brokenPayloads: make([]*ledger.Payload, 0, 10),
    50  	}
    51  }
    52  
    53  func (m *FixSlabsWithBrokenReferencesMigration) InitMigration(
    54  	log zerolog.Logger,
    55  	_ *registers.ByAccount,
    56  	nWorkers int,
    57  ) error {
    58  	m.log = log.
    59  		With().
    60  		Str("migration", fixSlabsWithBrokenReferencesName).
    61  		Logger()
    62  	m.nWorkers = nWorkers
    63  
    64  	return nil
    65  }
    66  
    67  func (m *FixSlabsWithBrokenReferencesMigration) MigrateAccount(
    68  	_ context.Context,
    69  	address common.Address,
    70  	accountRegisters *registers.AccountRegisters,
    71  ) error {
    72  
    73  	if _, exist := m.accountsToFix[address]; !exist {
    74  		return nil
    75  	}
    76  
    77  	migrationRuntime := NewBasicMigrationRuntime(accountRegisters)
    78  	storage := migrationRuntime.Storage
    79  
    80  	// Load all atree registers in storage
    81  	err := loadAtreeSlabsInStorage(storage, accountRegisters, m.nWorkers)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	// Fix broken references
    87  	fixedStorageIDs, skippedStorageIDs, err :=
    88  		storage.FixLoadedBrokenReferences(migrations.ShouldFixBrokenCompositeKeyedDictionary)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	if len(skippedStorageIDs) > 0 {
    94  		m.log.Warn().
    95  			Str("account", address.HexWithPrefix()).
    96  			Msgf("skipped slabs with broken references: %v", skippedStorageIDs)
    97  	}
    98  
    99  	if len(fixedStorageIDs) == 0 {
   100  		m.log.Warn().
   101  			Str("account", address.HexWithPrefix()).
   102  			Msgf("did not fix any slabs with broken references")
   103  
   104  		return nil
   105  	}
   106  
   107  	m.log.Log().
   108  		Str("account", address.HexWithPrefix()).
   109  		Msgf("fixed %d slabs with broken references", len(fixedStorageIDs))
   110  
   111  	// Save broken payloads to save to payload file later
   112  	brokenPayloads, err := getAtreePayloadsByID(accountRegisters, fixedStorageIDs)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	m.mergeBrokenPayloads(brokenPayloads)
   118  
   119  	err = storage.NondeterministicFastCommit(m.nWorkers)
   120  	if err != nil {
   121  		return fmt.Errorf("failed to commit storage: %w", err)
   122  	}
   123  
   124  	// Finalize the transaction
   125  	result, err := migrationRuntime.TransactionState.FinalizeMainTransaction()
   126  	if err != nil {
   127  		return fmt.Errorf("failed to finalize main transaction: %w", err)
   128  	}
   129  
   130  	// Merge the changes to the original payloads.
   131  	expectedAddresses := map[flow.Address]struct{}{
   132  		flow.Address(address): {},
   133  	}
   134  
   135  	err = registers.ApplyChanges(
   136  		accountRegisters,
   137  		result.WriteSet,
   138  		expectedAddresses,
   139  		m.log,
   140  	)
   141  	if err != nil {
   142  		return fmt.Errorf("failed to apply changes to account registers: %w", err)
   143  	}
   144  
   145  	// Log fixed payloads
   146  	fixedPayloads, err := getAtreePayloadsByID(accountRegisters, fixedStorageIDs)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	m.rw.Write(fixedSlabsWithBrokenReferences{
   152  		Account:        address.Hex(),
   153  		BrokenPayloads: brokenPayloads,
   154  		FixedPayloads:  fixedPayloads,
   155  	})
   156  
   157  	return nil
   158  }
   159  
   160  func (m *FixSlabsWithBrokenReferencesMigration) mergeBrokenPayloads(payloads []*ledger.Payload) {
   161  	m.mutex.Lock()
   162  	defer m.mutex.Unlock()
   163  
   164  	m.brokenPayloads = append(m.brokenPayloads, payloads...)
   165  }
   166  
   167  func (m *FixSlabsWithBrokenReferencesMigration) Close() error {
   168  	// close the report writer so it flushes to file
   169  	m.rw.Close()
   170  
   171  	err := m.writeBrokenPayloads()
   172  	if err != nil {
   173  		return fmt.Errorf("failed to write broken payloads to file: %w", err)
   174  	}
   175  
   176  	return nil
   177  }
   178  
   179  func (m *FixSlabsWithBrokenReferencesMigration) writeBrokenPayloads() error {
   180  
   181  	m.payloadsFile = path.Join(
   182  		m.outputDir,
   183  		fmt.Sprintf("broken_%d.payloads", int32(time.Now().Unix())),
   184  	)
   185  
   186  	writtenPayloadCount, err := util.CreatePayloadFile(
   187  		m.log,
   188  		m.payloadsFile,
   189  		m.brokenPayloads,
   190  		nil,
   191  		true,
   192  	)
   193  
   194  	if err != nil {
   195  		return fmt.Errorf("failed to write all broken payloads to file: %w", err)
   196  	}
   197  
   198  	if writtenPayloadCount != len(m.brokenPayloads) {
   199  		return fmt.Errorf(
   200  			"failed to write all broken payloads to file: expected %d, got %d",
   201  			len(m.brokenPayloads),
   202  			writtenPayloadCount,
   203  		)
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  func getAtreePayloadsByID(
   210  	registers *registers.AccountRegisters,
   211  	ids map[atree.SlabID][]atree.SlabID,
   212  ) (
   213  	[]*ledger.Payload,
   214  	error,
   215  ) {
   216  	outputPayloads := make([]*ledger.Payload, 0, len(ids))
   217  
   218  	err := registers.ForEach(func(owner string, key string, value []byte) error {
   219  
   220  		if !flow.IsSlabIndexKey(key) {
   221  			return nil
   222  		}
   223  
   224  		slabID := atree.NewSlabID(
   225  			atree.Address([]byte(owner)),
   226  			atree.SlabIndex([]byte(key[1:])),
   227  		)
   228  
   229  		_, ok := ids[slabID]
   230  		if !ok {
   231  			return nil
   232  		}
   233  
   234  		ledgerKey := convert.RegisterIDToLedgerKey(flow.RegisterID{
   235  			Owner: owner,
   236  			Key:   key,
   237  		})
   238  		payload := ledger.NewPayload(ledgerKey, value)
   239  		outputPayloads = append(outputPayloads, payload)
   240  
   241  		return nil
   242  	})
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	return outputPayloads, nil
   248  }
   249  
   250  type fixedSlabsWithBrokenReferences struct {
   251  	Account        string            `json:"account"`
   252  	BrokenPayloads []*ledger.Payload `json:"broken_payloads"`
   253  	FixedPayloads  []*ledger.Payload `json:"fixed_payloads"`
   254  }