-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Morse->Shannon Migration] state export/import - collect accounts #1039
base: scaffold/migration-module
Are you sure you want to change the base?
Changes from all commits
85992e2
890d5b5
95e1edf
77bc6a5
0d8505d
46c87be
178ded8
c2cca35
54fabd9
aa2859b
794b768
6ad5099
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package migrate | ||
|
||
import sdkerrors "cosmossdk.io/errors" | ||
|
||
const codespace = "poktrolld/migrate" | ||
|
||
var ( | ||
// ErrInvalidUsage usage is returned when the CLI arguments are invalid. | ||
ErrInvalidUsage = sdkerrors.Register(codespace, 1100, "invalid usage") | ||
// ErrMorseExportState is returned with the JSON generated from `pocket util export-genesis-for-reset` is invalid. | ||
ErrMorseExportState = sdkerrors.Register(codespace, 1101, "morse export state") | ||
// ErrMorseStateTransform is returned upon general failure when transforming the MorseExportState into the MorseAccountState. | ||
ErrMorseStateTransform = sdkerrors.Register(codespace, 1102, "morse state transform") | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,275 @@ | ||
package migrate | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
cosmosmath "cosmossdk.io/math" | ||
cmtjson "github.com/cometbft/cometbft/libs/json" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/pokt-network/poktroll/app/volatile" | ||
"github.com/pokt-network/poktroll/pkg/polylog" | ||
"github.com/pokt-network/poktroll/pkg/polylog/polyzero" | ||
migrationtypes "github.com/pokt-network/poktroll/x/migration/types" | ||
) | ||
|
||
var ( | ||
flagDebugAccountsPerLog int | ||
flagLogLevel string | ||
flagLogOutput string | ||
logger polylog.Logger | ||
|
||
collectMorseAccountsCmd = &cobra.Command{ | ||
Use: "collect-morse-accounts [morse-state-export-path] [morse-account-state-path]", | ||
Args: cobra.ExactArgs(2), | ||
Short: "Collect all account balances and corresponding stakes from the JSON file at [morse-state-export-path] and outputs them as JSON to [morse-account-state-path]", | ||
Long: `Collects the account balances and corresponding stakes from the MorseStateExport JSON file at morse-state-path | ||
and outputs them as a MorseAccountState JSON to morse-accounts-path for use with | ||
Shannon's MsgUploadMorseState. The Morse state export is generated via the Morse CLI: | ||
pocket util export-genesis-for-reset [height] [new-chain-id] > morse-state-export.json`, | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
var ( | ||
logOutput io.Writer | ||
err error | ||
) | ||
logLevel := polyzero.ParseLevel(flagLogLevel) | ||
if flagLogOutput == "-" { | ||
logOutput = os.Stderr | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be
|
||
} else { | ||
logOutput, err = os.Open(flagLogOutput) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
logger = polyzero.NewLogger( | ||
polyzero.WithLevel(logLevel), | ||
polyzero.WithOutput(logOutput), | ||
).With("cmd", "migrate") | ||
return nil | ||
}, | ||
RunE: runCollectMorseAccounts, | ||
} | ||
) | ||
|
||
func MigrateCmd() *cobra.Command { | ||
migrateCmd := &cobra.Command{ | ||
Use: "migrate", | ||
Short: "Migration commands", | ||
} | ||
migrateCmd.AddCommand(collectMorseAccountsCmd) | ||
migrateCmd.PersistentFlags().StringVar(&flagLogLevel, "log-level", "info", "The logging level (debug|info|warn|error)") | ||
migrateCmd.PersistentFlags().StringVar(&flagLogOutput, "log-output", "-", "The logging output (file path); defaults to stdout") | ||
|
||
collectMorseAccountsCmd.Flags().IntVar(&flagDebugAccountsPerLog, "debug-accounts-per-log", 0, "The number of accounts to log per debug message") | ||
|
||
return migrateCmd | ||
} | ||
|
||
// runCollectedMorseAccounts is run by the `poktrolld migrate collect-morse-accounts` command. | ||
func runCollectMorseAccounts(_ *cobra.Command, args []string) error { | ||
// DEV_NOTE: No need to check args length due to cobra.ExactArgs(2). | ||
morseStateExportPath := args[0] | ||
morseAccountStatePath := args[1] | ||
|
||
logger.Info(). | ||
Str("morse_state_export_path", morseStateExportPath). | ||
Str("morse_account_state_path", morseAccountStatePath). | ||
Msg("collecting Morse accounts...") | ||
|
||
morseWorkspace, err := collectMorseAccounts(morseStateExportPath, morseAccountStatePath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return morseWorkspace.infoLogComplete() | ||
} | ||
|
||
// collectMorseAccounts reads and transforms the JSON serialized MorseStateExport | ||
// at morseStateExportPath into a JSON serialized MorseAccountState, and then writes | ||
// it to morseAccountStatePath. | ||
func collectMorseAccounts(morseStateExportPath, morseAccountStatePath string) (*morseImportWorkspace, error) { | ||
if err := validatePathIsFile(morseStateExportPath); err != nil { | ||
return nil, err | ||
} | ||
|
||
inputStateJSON, err := os.ReadFile(morseStateExportPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
inputState := new(migrationtypes.MorseStateExport) | ||
if err = cmtjson.Unmarshal(inputStateJSON, inputState); err != nil { | ||
return nil, err | ||
} | ||
|
||
morseWorkspace := newMorseImportWorkspace() | ||
if err = transformMorseState(inputState, morseWorkspace); err != nil { | ||
return nil, err | ||
} | ||
|
||
outputStateJSONBz, err := cmtjson.Marshal(morseWorkspace.accountState) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 👍 |
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err = os.WriteFile(morseAccountStatePath, outputStateJSONBz, 0644); err != nil { | ||
return nil, err | ||
} | ||
|
||
return morseWorkspace, nil | ||
} | ||
|
||
// validatePathIsFile returns an error if the given path does not exist or is not a file. | ||
func validatePathIsFile(path string) error { | ||
info, err := os.Stat(path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if info.IsDir() { | ||
return ErrInvalidUsage.Wrapf("[morse-JSON-input-path] cannot be a directory: %s", path) | ||
} | ||
return nil | ||
} | ||
|
||
// transformMorseState consolidates the Morse account balance, application stake, | ||
// and supplier stake for each account as an entry in the resulting MorseAccountState. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about validator stake? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding is that "supplier stake" is identical to "validator stake" in Morse terms. I intentionally chose Shannon terms for all the comments for consistency. |
||
func transformMorseState( | ||
inputState *migrationtypes.MorseStateExport, | ||
morseWorkspace *morseImportWorkspace, | ||
) error { | ||
// Iterate over accounts and copy the balances. | ||
logger.Info().Msg("collecting account balances...") | ||
if err := collectInputAccountBalances(inputState, morseWorkspace); err != nil { | ||
return err | ||
} | ||
|
||
// Iterate over applications and add the stakes to the corresponding account balances. | ||
logger.Info().Msg("collecting application stakes...") | ||
if err := collectInputApplicationStakes(inputState, morseWorkspace); err != nil { | ||
return err | ||
} | ||
|
||
// Iterate over suppliers and add the stakes to the corresponding account balances. | ||
logger.Info().Msg("collecting supplier stakes...") | ||
err := collectInputSupplierStakes(inputState, morseWorkspace) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
morseWorkspace.accountState = &migrationtypes.MorseAccountState{Accounts: morseWorkspace.accounts} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you #PUC why this is needed? |
||
return nil | ||
} | ||
|
||
// collectInputAccountBalances iterates over the accounts in the inputState and | ||
// adds the balances to the corresponding account balances in the morseWorkspace. | ||
func collectInputAccountBalances(inputState *migrationtypes.MorseStateExport, morseWorkspace *morseImportWorkspace) error { | ||
for exportAccountIdx, exportAccount := range inputState.AppState.Auth.Accounts { | ||
if shouldDebugLogProgress(exportAccountIdx) { | ||
morseWorkspace.debugLogProgress(exportAccountIdx) | ||
} | ||
|
||
// DEV_NOTE: Ignore module accounts. | ||
if exportAccount.Type != "posmint/Account" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this a module account? I did a grep and found my way here: https://github.com/pokt-network/pocket-core/blob/35c05eb89ec30b50239b24730d0aeab0002cfde2/x/auth/types/codec.go#L16 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The question isn't quite clear to me. The Morse data structure With respect to the morse state export / account state import, my understanding is that we're only interested in externally owned accounts. Do you see a reason to migrate module accounts as well? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're asking how I determined that a type value of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not sure if they should be migrated but they might (@Olshansk ?) contain funds. This makes me think we should validate the total supply returned by Morse vs. the supply we get by summing all accounts balances in the resulting Or, are we OK not having the same total supply after the migration? |
||
logger.Warn(). | ||
Str("type", exportAccount.Type). | ||
Str("address", exportAccount.Value.Address.String()). | ||
Str("coins", fmt.Sprintf("%s", exportAccount.Value.Coins)). | ||
Msg("ignoring non-EOA account") | ||
continue | ||
} | ||
|
||
accountAddr := exportAccount.Value.Address.String() | ||
if _, _, err := morseWorkspace.ensureAccount(accountAddr, exportAccount); err != nil { | ||
return err | ||
} | ||
|
||
coins := exportAccount.Value.Coins | ||
if len(coins) == 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This case is already handled by the We shouldn't |
||
return nil | ||
bryanchriswhite marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// DEV_NOTE: SHOULD ONLY be one denom (upokt). | ||
if len(coins) != 1 { | ||
return ErrMorseExportState.Wrapf( | ||
"account %q has %d token denominations, expected upokt only: %s", | ||
accountAddr, len(coins), coins, | ||
) | ||
} | ||
|
||
coin := coins[0] | ||
bryanchriswhite marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if coin.Denom != volatile.DenomuPOKT { | ||
return ErrMorseExportState.Wrapf("unsupported denom %q", coin.Denom) | ||
} | ||
|
||
if err := morseWorkspace.addUpokt(accountAddr, coin.Amount); err != nil { | ||
return fmt.Errorf( | ||
"adding morse account balance (%s) to account balance of address %q: %w", | ||
coin, accountAddr, err, | ||
) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// shouldDebugLogProgress returns true if the given exportAccountIdx should be logged | ||
// via debugLogProgress. | ||
func shouldDebugLogProgress(exportAccountIdx int) bool { | ||
return flagDebugAccountsPerLog > 0 && | ||
exportAccountIdx%flagDebugAccountsPerLog == 0 | ||
} | ||
|
||
// collectInputApplicationStakes iterates over the applications in the inputState and | ||
// adds the stake to the corresponding account balances in the morseWorkspace. | ||
func collectInputApplicationStakes(inputState *migrationtypes.MorseStateExport, morseWorkspace *morseImportWorkspace) error { | ||
for _, exportApplication := range inputState.AppState.Application.Applications { | ||
appAddr := exportApplication.Address.String() | ||
|
||
// DEV_NOTE: An account SHOULD exist for each actor. | ||
bryanchriswhite marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if !morseWorkspace.hasAccount(appAddr) { | ||
return ErrMorseExportState.Wrapf("account not found corresponding to application with address %q", appAddr) | ||
} | ||
|
||
appStakeAmtUpokt, ok := cosmosmath.NewIntFromString(exportApplication.StakedTokens) | ||
if !ok { | ||
return ErrMorseExportState.Wrapf("failed to parse application stake amount %q", exportApplication.StakedTokens) | ||
} | ||
|
||
if err := morseWorkspace.addUpokt(appAddr, appStakeAmtUpokt); err != nil { | ||
return fmt.Errorf( | ||
"adding application stake amount to account balance of address %q: %w", | ||
appAddr, err, | ||
) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// collectInputSupplierStakes iterates over the suppliers in the inputState and | ||
// adds the stake to the corresponding account balances in the morseWorkspace. | ||
func collectInputSupplierStakes(inputState *migrationtypes.MorseStateExport, morseWorkspace *morseImportWorkspace) error { | ||
for _, exportSupplier := range inputState.AppState.Pos.Validators { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ShannonSuppliers == MorseNode (aka Morse Servicer) Only top 1000 of staked validators are ACTUAL validators in Morse There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My understanding was that Morse supplier/node/servicer actors are all tendermint validators, and that tendermint uses sortition over the all validators to determine the active/voting set. I would have to look deeper into how Morse handles supplier staking to confirm/deny. According to tendermint v0.34 docs, the only ways for validators to be added are via genesis or an EndBlock message. |
||
supplierAddr := exportSupplier.Address.String() | ||
|
||
// DEV_NOTE: An account SHOULD exist for each actor. | ||
if !morseWorkspace.hasAccount(supplierAddr) { | ||
return ErrMorseExportState.Wrapf("account not found corresponding to supplier with address %q", supplierAddr) | ||
} | ||
|
||
supplierStakeAmtUpokt, ok := cosmosmath.NewIntFromString(exportSupplier.StakedTokens) | ||
if !ok { | ||
return ErrMorseExportState.Wrapf("failed to parse supplier stake amount %q", exportSupplier.StakedTokens) | ||
} | ||
|
||
if err := morseWorkspace.addUpokt(supplierAddr, supplierStakeAmtUpokt); err != nil { | ||
return fmt.Errorf( | ||
"adding supplier stake amount to account balance of address %q: %w", | ||
supplierAddr, err, | ||
) | ||
} | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that this is all new code, let's use autocli instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AutoCLI does not apply here because there is no gRPC service, message, or query.
The purpose of this command is to facilitate the deterministic (i.e. reproducible) transformation from the Morse export data structure (
MorseStateExport
) into the Shannon import data structure (MorseAccountState
). It does not interact with the network directly.