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 committed Apr 22, 2024
1 parent dfb68b2 commit 31fce07
Show file tree
Hide file tree
Showing 5 changed files with 419 additions and 1 deletion.
5 changes: 5 additions & 0 deletions cmd/util/cmd/execution-state-extract/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ var (
flagOutputPayloadFileName string
flagOutputPayloadByAddresses string
flagMaxAccountSize uint64
flagFixSlabWithBrokenReferences bool
)

var Cmd = &cobra.Command{
Expand Down Expand Up @@ -151,6 +152,9 @@ func init() {

Cmd.Flags().Uint64Var(&flagMaxAccountSize, "max-account-size", 0,
"max account size")

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 @@ -371,6 +375,7 @@ func run(*cobra.Command, []string) {
Prune: flagPrune,
MaxAccountSize: flagMaxAccountSize,
VerboseErrorOutput: flagVerboseErrorOutput,
FixSlabWithBrokenReferences: chainID == flow.Testnet && flagFixSlabWithBrokenReferences,
}

if len(flagInputPayloadFileName) > 0 {
Expand Down
22 changes: 21 additions & 1 deletion cmd/util/ledger/migrations/account_based_migration.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,27 @@ func MigrateGroupConcurrently(
return migrated, nil
}

var knownProblematicAccounts = map[common.Address]string{}
var knownProblematicAccounts = map[common.Address]string{
// Testnet accounts with broken contracts
mustHexToAddress("434a1f199a7ae3ba"): "Broken contract FanTopPermission",
mustHexToAddress("454c9991c2b8d947"): "Broken contract Test",
mustHexToAddress("48602d8056ff9d93"): "Broken contract FanTopPermission",
mustHexToAddress("5d63c34d7f05e5a4"): "Broken contract FanTopPermission",
mustHexToAddress("5e3448b3cffb97f2"): "Broken contract FanTopPermission",
mustHexToAddress("7d8c7e050c694eaa"): "Broken contract Test",
mustHexToAddress("ba53f16ede01972d"): "Broken contract FanTopPermission",
mustHexToAddress("c843c1f5a4805c3a"): "Broken contract FanTopPermission",
mustHexToAddress("48d3be92e6e4a973"): "Broken contract FanTopPermission",
// Mainnet account
}

func mustHexToAddress(hex string) common.Address {
address, err := common.HexToAddress(hex)
if err != nil {
panic(err)
}
return address
}

type jobMigrateAccountGroup struct {
Address common.Address
Expand Down
17 changes: 17 additions & 0 deletions cmd/util/ledger/migrations/cadence.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,7 @@ type Options struct {
StagedContracts []StagedContract
Prune bool
MaxAccountSize uint64
FixSlabWithBrokenReferences bool
}

func NewCadence1Migrations(
Expand Down Expand Up @@ -412,6 +413,22 @@ func NewCadence1Migrations(
)
}

if opts.FixSlabWithBrokenReferences {
migrations = append(migrations, NamedMigration{
Name: "clean-state",
Migrate: NewAccountBasedMigration(
log,
opts.NWorker,
[]AccountBasedMigration{
NewFixBrokenReferencesInSlabsMigration(rwf, knownProblematicAccounts),
// TODO: add migration to filter unreferenced slabs here.
// NOTE: migration to filter unreferenced slabs should happen
// after migration to fix slabs with references to nonexistent slabs.
},
),
})
}

if opts.Prune {
migration := NewCadence1PruneMigration(opts.ChainID, log)
if migration != nil {
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 31fce07

Please sign in to comment.