Skip to content

Commit

Permalink
Add migration to fix refs to non-existent registers
Browse files Browse the repository at this point in the history
This commit adds --fix-testnet-slabs-with-broken-references flag
to the migration program.

It uses a feature from onflow/atree#388 to fix 10 testnet registers
affecting 9 testnet accounts.

The 9 testnet accounts are hardcoded in this commit to prevent
accidentally applying this fix to any other accounts.

In testnet, broken references seem to have resulted from a bug
that was fixed 2 years ago by onflow/cadence#1565.

A broken reference is a `StorageID` referencing a non-existent register.
So far, only 10 registers in testnet (none on mainnet) were found to
contain broken references.
  • Loading branch information
fxamacker authored and turbolent committed Apr 23, 2024
1 parent 50d87db commit cd355d4
Show file tree
Hide file tree
Showing 4 changed files with 388 additions and 0 deletions.
10 changes: 10 additions & 0 deletions cmd/util/cmd/execution-state-extract/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var (
flagInputPayloadFileName string
flagOutputPayloadFileName string
flagOutputPayloadByAddresses string
flagFixSlabWithBrokenReferences bool
)

var Cmd = &cobra.Command{
Expand Down Expand Up @@ -122,6 +123,9 @@ func init() {
"",
"extract payloads of addresses (comma separated hex-encoded addresses) to file specified by output-payload-filename",
)

Cmd.Flags().BoolVar(&flagFixSlabWithBrokenReferences, "fix-testnet-slabs-with-broken-references", false,
"fix slabs with broken references in testnet")
}

func run(*cobra.Command, []string) {
Expand Down Expand Up @@ -299,6 +303,10 @@ func run(*cobra.Command, []string) {

log.Info().Msgf("state extraction plan: %s, %s", inputMsg, outputMsg)


chainID := flow.ChainID(flagChain)
fixSlabWithBrokenReferences := chainID == flow.Testnet && flagFixSlabWithBrokenReferences

var err error
if len(flagInputPayloadFileName) > 0 {
err = extractExecutionStateFromPayloads(
Expand All @@ -310,6 +318,7 @@ func run(*cobra.Command, []string) {
flagInputPayloadFileName,
flagOutputPayloadFileName,
exportedAddresses,
fixSlabWithBrokenReferences,
)
} else {
err = extractExecutionState(
Expand All @@ -321,6 +330,7 @@ func run(*cobra.Command, []string) {
!flagNoMigration,
flagOutputPayloadFileName,
exportedAddresses,
fixSlabWithBrokenReferences,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func extractExecutionState(
runMigrations bool,
outputPayloadFile string,
exportPayloadsByAddresses []common.Address,
fixSlabWithBrokenReferences bool,
) error {

log.Info().Msg("init WAL")
Expand Down Expand Up @@ -213,6 +214,7 @@ func extractExecutionStateFromPayloads(
inputPayloadFile string,
outputPayloadFile string,
exportPayloadsByAddresses []common.Address,
fixSlabWithBrokenReferences bool,
) error {

inputPayloadsFromPartialState, payloads, err := util.ReadPayloadFile(log, inputPayloadFile)
Expand Down
191 changes: 191 additions & 0 deletions cmd/util/ledger/migrations/fix_broken_data_migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package migrations

import (
"context"
"fmt"

"github.com/rs/zerolog"

"github.com/onflow/atree"

"github.com/onflow/cadence/runtime/common"

"github.com/onflow/flow-go/cmd/util/ledger/reporters"
"github.com/onflow/flow-go/ledger"
"github.com/onflow/flow-go/ledger/common/convert"
"github.com/onflow/flow-go/model/flow"
)

type FixSlabsWithBrokenReferencesMigration struct {
log zerolog.Logger
rw reporters.ReportWriter
accountsToFix map[common.Address]string
nWorkers int
}

var _ AccountBasedMigration = &FixSlabsWithBrokenReferencesMigration{}

const fixSlabsWithBrokenReferencesName = "fix-slabs-with-broken-references"

func NewFixBrokenReferencesInSlabsMigration(
rwf reporters.ReportWriterFactory,
accountsToFix map[common.Address]string,
) *FixSlabsWithBrokenReferencesMigration {
return &FixSlabsWithBrokenReferencesMigration{
rw: rwf.ReportWriter(fixSlabsWithBrokenReferencesName),
accountsToFix: accountsToFix,
}
}

func (m *FixSlabsWithBrokenReferencesMigration) InitMigration(
log zerolog.Logger,
_ []*ledger.Payload,
nWorkers int,
) error {
m.log = log.
With().
Str("migration", fixSlabsWithBrokenReferencesName).
Logger()
m.nWorkers = nWorkers

return nil
}

func (m *FixSlabsWithBrokenReferencesMigration) MigrateAccount(
_ context.Context,
address common.Address,
oldPayloads []*ledger.Payload,
) (
newPayloads []*ledger.Payload,
err error,
) {

if _, exist := m.accountsToFix[address]; !exist {
return oldPayloads, nil
}

migrationRuntime, err := NewAtreeRegisterMigratorRuntime(address, oldPayloads)
if err != nil {
return nil, fmt.Errorf("failed to create cadence runtime: %w", err)
}

storage := migrationRuntime.Storage

// Load all atree registers in storage
for _, payload := range oldPayloads {
registerID, _, err := convert.PayloadToRegister(payload)
if err != nil {
return nil, fmt.Errorf("failed to convert payload to register: %w", err)
}

if !registerID.IsSlabIndex() {
continue
}

// Convert the register ID to a storage ID.
slabID := atree.NewStorageID(
atree.Address([]byte(registerID.Owner)),
atree.StorageIndex([]byte(registerID.Key[1:])))

// Retrieve the slab.
_, _, err = storage.Retrieve(slabID)
if err != nil {
return nil, fmt.Errorf("failed to retrieve slab %s: %w", slabID, err)
}
}

// Fix broken references
fixedStorageIDs, skippedStorageIDs, err := storage.FixLoadedBrokenReferences(func(old atree.Value) bool {
// TODO: Cadence may need to export functions to check type info, etc.
return true
})
if err != nil {
return nil, err
}

if len(skippedStorageIDs) > 0 {
m.log.Warn().
Str("account", address.Hex()).
Msgf("skipped slabs with broken references: %v", skippedStorageIDs)
}

if len(fixedStorageIDs) == 0 {
m.log.Warn().
Str("account", address.Hex()).
Msgf("did not fix any slabs with broken references")

return oldPayloads, nil
}

m.log.Log().
Str("account", address.Hex()).
Msgf("fixed slabs with broken references: %v", fixedStorageIDs)

err = storage.FastCommit(m.nWorkers)
if err != nil {
return nil, err
}

// Finalize the transaction
result, err := migrationRuntime.TransactionState.FinalizeMainTransaction()
if err != nil {
return nil, fmt.Errorf("failed to finalize main transaction: %w", err)
}

// Merge the changes to the original payloads.
expectedAddresses := map[flow.Address]struct{}{
flow.Address(address): {},
}

newPayloads, err = migrationRuntime.Snapshot.ApplyChangesAndGetNewPayloads(
result.WriteSet,
expectedAddresses,
m.log,
)
if err != nil {
return nil, err
}

// Log fixed payloads
fixedPayloads := make([]*ledger.Payload, 0, len(fixedStorageIDs))
for _, payload := range newPayloads {
registerID, _, err := convert.PayloadToRegister(payload)
if err != nil {
return nil, fmt.Errorf("failed to convert payload to register: %w", err)
}

if !registerID.IsSlabIndex() {
continue
}

storageID := atree.NewStorageID(
atree.Address([]byte(registerID.Owner)),
atree.StorageIndex([]byte(registerID.Key[1:])),
)

if _, ok := fixedStorageIDs[storageID]; ok {
fixedPayloads = append(fixedPayloads, payload)
}
}

m.rw.Write(fixedSlabsWithBrokenReferences{
Account: address,
Payloads: fixedPayloads,
Msg: m.accountsToFix[address],
})

return newPayloads, nil
}

func (m *FixSlabsWithBrokenReferencesMigration) Close() error {
// close the report writer so it flushes to file
m.rw.Close()

return nil
}

type fixedSlabsWithBrokenReferences struct {
Account common.Address `json:"account"`
Payloads []*ledger.Payload `json:"payloads"`
Msg string `json:"msg"`
}
Loading

0 comments on commit cd355d4

Please sign in to comment.