Skip to content
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] feat: implement Morse account state upload #1047

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
71f5112
refactor: fixture generator testutil
bryanchriswhite Jan 29, 2025
0180b2f
feat: implement Morse account state upload
bryanchriswhite Jan 29, 2025
7c42e21
fix: typo
bryanchriswhite Jan 29, 2025
1add213
chore: comments
bryanchriswhite Jan 29, 2025
132938d
Merge branch 'issues/1034/scaffold/morse_account_state' into issues/1…
bryanchriswhite Feb 3, 2025
dad4fe4
chore: improvements
bryanchriswhite Feb 3, 2025
e0a6d4b
chore: review feedback improvements
bryanchriswhite Feb 3, 2025
923373b
chore: review improvements
bryanchriswhite Feb 3, 2025
31f879a
Merge branch 'issues/1034/scaffold/morse_account_state' into issues/1…
bryanchriswhite Feb 3, 2025
7ef355f
Merge branch 'issues/1034/scaffold/morse_account_state' into issues/1…
bryanchriswhite Feb 4, 2025
1a314f1
chore: review improvements
bryanchriswhite Feb 4, 2025
3009a85
fix: delete unused
bryanchriswhite Feb 4, 2025
f7fc70a
chore: review improvements
bryanchriswhite Feb 4, 2025
b8c2eff
Merge branch 'issues/1034/scaffold/morse_account_state' into issues/1…
bryanchriswhite Feb 5, 2025
a822c23
Merge branch 'chore/migration/state-prep' into issues/1034/scaffold/m…
bryanchriswhite Feb 6, 2025
a80f643
Merge remote-tracking branch 'pokt/issues/1034/scaffold/morse_account…
bryanchriswhite Feb 6, 2025
e38baee
fix: autocli
bryanchriswhite Feb 6, 2025
170875e
Merge branch 'issues/1034/scaffold/morse_account_state' into issues/1…
bryanchriswhite Feb 6, 2025
aade27e
Merge branch 'issues/1034/scaffold/morse_account_state' into issues/1…
bryanchriswhite Feb 10, 2025
59cada2
chore: review feedback improvements
bryanchriswhite Feb 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
713 changes: 713 additions & 0 deletions api/poktroll/migration/event.pulsar.go

Large diffs are not rendered by default.

101 changes: 4 additions & 97 deletions cmd/poktrolld/cmd/migrate/migrate_test.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
package migrate

import (
"encoding/binary"
"fmt"
"math"
"math/rand"
"os"
"path/filepath"
"strings"
"testing"

cometcrypto "github.com/cometbft/cometbft/crypto/ed25519"
cmtjson "github.com/cometbft/cometbft/libs/json"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
"github.com/regen-network/gocuke"
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/app/volatile"
"github.com/pokt-network/poktroll/pkg/polylog/polyzero"
"github.com/pokt-network/poktroll/testutil/testmigration"
migrationtypes "github.com/pokt-network/poktroll/x/migration/types"
)

Expand All @@ -32,7 +27,7 @@ func TestCollectMorseAccounts(t *testing.T) {
inputFile, err := os.CreateTemp(tmpDir, "morse-state-input.json")
require.NoError(t, err)

morseStateExportBz, morseAccountStateBz := newMorseStateExportAndAccountState(t, 10)
morseStateExportBz, morseAccountStateBz := testmigration.NewMorseStateExportAndAccountStateBytes(t, 10)
_, err = inputFile.Write(morseStateExportBz)
require.NoError(t, err)

Expand Down Expand Up @@ -61,7 +56,7 @@ func TestNewTestMorseStateExport(t *testing.T) {
for i := 1; i < 10; i++ {
t.Run(fmt.Sprintf("num_accounts=%d", i), func(t *testing.T) {
morseStateExport := new(migrationtypes.MorseStateExport)
stateExportBz, _ := newMorseStateExportAndAccountState(t, i)
stateExportBz, _ := testmigration.NewMorseStateExportAndAccountStateBytes(t, i)
err := cmtjson.Unmarshal(stateExportBz, morseStateExport)
require.NoError(t, err)

Expand All @@ -83,7 +78,7 @@ func BenchmarkTransformMorseState(b *testing.B) {
for i := 0; i < 5; i++ {
numAccounts := int(math.Pow10(i + 1))
morseStateExport := new(migrationtypes.MorseStateExport)
morseStateExportBz, _ := newMorseStateExportAndAccountState(b, numAccounts)
morseStateExportBz, _ := testmigration.NewMorseStateExportAndAccountStateBytes(b, numAccounts)
err := cmtjson.Unmarshal(morseStateExportBz, morseStateExport)
require.NoError(b, err)

Expand All @@ -99,91 +94,3 @@ func BenchmarkTransformMorseState(b *testing.B) {
})
}
}

// TODO_CONSIDERATION: Test/benchmark execution speed can be optimized by refactoring this to a pre-generate fixture.
func newMorseStateExportAndAccountState(
t gocuke.TestingT,
numAccounts int,
) (morseStateExportBz []byte, morseAccountStateBz []byte) {
morseStateExport := &migrationtypes.MorseStateExport{
AppHash: "",
AppState: &migrationtypes.MorseTendermintAppState{
Application: &migrationtypes.MorseApplications{},
Auth: &migrationtypes.MorseAuth{},
Pos: &migrationtypes.MorsePos{},
},
}

morseAccountState := &migrationtypes.MorseAccountState{
Accounts: make([]*migrationtypes.MorseAccount, numAccounts),
}

for i := 1; i < numAccounts+1; i++ {
seedUint := rand.Uint64()
seedBz := make([]byte, 8)
binary.LittleEndian.PutUint64(seedBz, seedUint)
privKey := cometcrypto.GenPrivKeyFromSecret(seedBz)
pubKey := privKey.PubKey()
balanceAmount := int64(1e6*i + i) // i_000_00i
appStakeAmount := int64(1e5*i + (i * 10)) // i00_0i0
supplierStakeAmount := int64(1e4*i + (i * 100)) // i0_i00
sumAmount := balanceAmount + appStakeAmount + supplierStakeAmount // i_ii0_iii

// Add an account.
morseStateExport.AppState.Auth.Accounts = append(
morseStateExport.AppState.Auth.Accounts,
&migrationtypes.MorseAuthAccount{
Type: "posmint/Account",
Value: &migrationtypes.MorseAccount{
Address: pubKey.Address(),
Coins: cosmostypes.NewCoins(cosmostypes.NewInt64Coin(volatile.DenomuPOKT, balanceAmount)),
PubKey: &migrationtypes.MorsePublicKey{
Value: pubKey.Bytes(),
},
},
},
)

// Add an application.
morseStateExport.AppState.Application.Applications = append(
morseStateExport.AppState.Application.Applications,
&migrationtypes.MorseApplication{
Address: pubKey.Address(),
PublicKey: pubKey.Bytes(),
Jailed: false,
Status: 2,
StakedTokens: fmt.Sprintf("%d", appStakeAmount),
},
)

// Add a supplier.
morseStateExport.AppState.Pos.Validators = append(
morseStateExport.AppState.Pos.Validators,
&migrationtypes.MorseValidator{
Address: pubKey.Address(),
PublicKey: pubKey.Bytes(),
Jailed: false,
Status: 2,
StakedTokens: fmt.Sprintf("%d", supplierStakeAmount),
},
)

// Add the account to the morseAccountState.
morseAccountState.Accounts[i-1] = &migrationtypes.MorseAccount{
Address: pubKey.Address(),
Coins: cosmostypes.NewCoins(cosmostypes.NewInt64Coin(volatile.DenomuPOKT, sumAmount)),
PubKey: &migrationtypes.MorsePublicKey{
Value: pubKey.Bytes(),
},
}
}

var err error
morseStateExportBz, err = cmtjson.Marshal(morseStateExport)
require.NoError(t, err)

morseAccountStateBz, err = cmtjson.Marshal(morseAccountState)
require.NoError(t, err)

return morseStateExportBz, morseAccountStateBz
}
25 changes: 25 additions & 0 deletions proto/poktroll/migration/event.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
syntax = "proto3";
package poktroll.migration;

option go_package = "github.com/pokt-network/poktroll/x/migration/types";
option (gogoproto.stable_marshaler_all) = true;

import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";

import "poktroll/shared/service.proto";
import "poktroll/migration/morse_onchain.proto";

// EventUploadMorseState is emitted when the MorseAccountState is created on-chain.
message EventCreateMorseAccountState {
// The height (on Shannon) at which the MorseAccountState was created on-chain.
int64 created_at_height = 1 [(gogoproto.jsontag) = "created_at_height"];

// The sha256 has of the MorseAccountState.
bytes morse_account_state_hash = 2 [(gogoproto.jsontag) = "morse_account_state_hash"];

// The number of accounts (EOAs) which were collected from the Morse state export, which may be claimed.
// NOTE: Application and supplier actor stakes are consolidated into their corresponding account balances.
uint64 num_accounts = 3 [(gogoproto.jsontag) = "num_accounts"];
}
132 changes: 132 additions & 0 deletions testutil/testmigration/fixtures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package testmigration

import (
"encoding/binary"
"fmt"
"math/rand"

cometcrypto "github.com/cometbft/cometbft/crypto/ed25519"
cmtjson "github.com/cometbft/cometbft/libs/json"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
"github.com/regen-network/gocuke"
"github.com/stretchr/testify/require"

"github.com/pokt-network/poktroll/app/volatile"
migrationtypes "github.com/pokt-network/poktroll/x/migration/types"
)

// NewMorseStateExportAndAccountStateBytes returns:
// - A serialized MorseStateExport.
// This is the JSON output of `pocket util export-genesis-for-reset`.
// It is used to generate the MorseAccountState.
// - Its corresponding MorseAccountState.
// This is the JSON output of `poktrolld migrate collect-morse-accounts`.
// It is used to persist the canonical Morse migration state from on Shannon.
//
// The states are populated with:
// - Random account addresses
// - Monotonically increasing balances/stakes
// - One application per account
// - One supplier per account
func NewMorseStateExportAndAccountStateBytes(
t gocuke.TestingT,
numAccounts int,
) (morseStateExportBz []byte, morseAccountStateBz []byte) {
morseStateExport, morseAccountState := NewMorseStateExportAndAccountState(t, numAccounts)

var err error
morseStateExportBz, err = cmtjson.Marshal(morseStateExport)
require.NoError(t, err)

morseAccountStateBz, err = cmtjson.Marshal(morseAccountState)
require.NoError(t, err)

return morseStateExportBz, morseAccountStateBz
}

// NewMorseStateExportAndAccountState returns MorseStateExport and MorseAccountState
bryanchriswhite marked this conversation as resolved.
Show resolved Hide resolved
// structs populated with:
// - Random account addresses
// - Monotonically increasing balances/stakes
// - One application per account
// - One supplier per account
func NewMorseStateExportAndAccountState(
t gocuke.TestingT, numAccounts int,
) (export *migrationtypes.MorseStateExport, state *migrationtypes.MorseAccountState) {
t.Helper()

morseStateExport := &migrationtypes.MorseStateExport{
AppHash: "",
AppState: &migrationtypes.MorseTendermintAppState{
Application: &migrationtypes.MorseApplications{},
Auth: &migrationtypes.MorseAuth{},
Pos: &migrationtypes.MorsePos{},
},
}

morseAccountState := &migrationtypes.MorseAccountState{
Accounts: make([]*migrationtypes.MorseAccount, numAccounts),
}

for i := 1; i < numAccounts+1; i++ {
seedUint := rand.Uint64()
seedBz := make([]byte, 8)
binary.LittleEndian.PutUint64(seedBz, seedUint)
privKey := cometcrypto.GenPrivKeyFromSecret(seedBz)
pubKey := privKey.PubKey()
balanceAmount := int64(1e6*i + i) // i_000_00i
appStakeAmount := int64(1e5*i + (i * 10)) // i00_0i0
supplierStakeAmount := int64(1e4*i + (i * 100)) // i0_i00
sumAmount := balanceAmount + appStakeAmount + supplierStakeAmount // i_ii0_iii

// Add an account.
morseStateExport.AppState.Auth.Accounts = append(
morseStateExport.AppState.Auth.Accounts,
&migrationtypes.MorseAuthAccount{
Type: "posmint/Account",
Value: &migrationtypes.MorseAccount{
Address: pubKey.Address(),
Coins: cosmostypes.NewCoins(cosmostypes.NewInt64Coin(volatile.DenomuPOKT, balanceAmount)),
PubKey: &migrationtypes.MorsePublicKey{
Value: pubKey.Bytes(),
},
},
},
)

// Add an application.
morseStateExport.AppState.Application.Applications = append(
morseStateExport.AppState.Application.Applications,
&migrationtypes.MorseApplication{
Address: pubKey.Address(),
PublicKey: pubKey.Bytes(),
Jailed: false,
Status: 2,
StakedTokens: fmt.Sprintf("%d", appStakeAmount),
},
)

// Add a supplier.
morseStateExport.AppState.Pos.Validators = append(
morseStateExport.AppState.Pos.Validators,
&migrationtypes.MorseValidator{
Address: pubKey.Address(),
PublicKey: pubKey.Bytes(),
Jailed: false,
Status: 2,
StakedTokens: fmt.Sprintf("%d", supplierStakeAmount),
},
)

// Add the account to the morseAccountState.
morseAccountState.Accounts[i-1] = &migrationtypes.MorseAccount{
Address: pubKey.Address(),
Coins: cosmostypes.NewCoins(cosmostypes.NewInt64Coin(volatile.DenomuPOKT, sumAmount)),
PubKey: &migrationtypes.MorsePublicKey{
Value: pubKey.Bytes(),
},
}
}

return morseStateExport, morseAccountState
}
56 changes: 38 additions & 18 deletions x/migration/keeper/msg_server_morse_account_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,54 @@ package keeper
import (
"context"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/pokt-network/poktroll/x/migration/types"
migrationtypes "github.com/pokt-network/poktroll/x/migration/types"
)

// CreateMorseAccountState creates the on-chain MorseAccountState ONLY ONCE (per network / re-genesis).
func (k msgServer) CreateMorseAccountState(goCtx context.Context, msg *types.MsgCreateMorseAccountState) (*types.MsgCreateMorseAccountStateResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
func (k msgServer) CreateMorseAccountState(
ctx context.Context,
msg *migrationtypes.MsgCreateMorseAccountState,
) (*migrationtypes.MsgCreateMorseAccountStateResponse, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
bryanchriswhite marked this conversation as resolved.
Show resolved Hide resolved
logger := sdkCtx.Logger().With("method", "CreateMorseAccountState")

// Check if the value already exists
_, isFound := k.GetMorseAccountState(ctx)
if isFound {
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "already set")
if err := msg.ValidateBasic(); err != nil {
logger.Info(err.Error())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why is this not logger.Error?
  2. Can you add a bit of a preeamble: "Failed basic message validation in CreateMorseAccountState due to %v"

Copy link
Contributor Author

@bryanchriswhite bryanchriswhite Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why is this not logger.Error?

logger.Error() should ONLY be used for server-side errors (i.e. operator attention required), not client-side errors.

Node operators are the primary "user" (in a UX sense) of the log output. As such, any error which does not require operator attention SHOULD NOT use any level other than "debug"; however, due to reasons outlined in #856, we've decided to use the "info" level on-chain for both "info" and "debug" logs.

  1. Can you add a bit of a preeamble: "Failed basic message validation in CreateMorseAccountState due to %v"
  1. The fact that we're in the CreateMorseAccountState method is already captured by the .With("method", ...) above.
  2. I'm not convinced that this suggestion adds any more detail/value. The errors returned in this case would be one of:
    • sdkerrors.ErrInvalidAddress.Wrapf("invalid authority address (%s)", err)
    • ErrMorseAccountState.Wrapf("expected hash is empty")
    • ErrMorseAccountState.Wrapf("Morse account state hash (%x) doesn't match expected: (%x)", actualHash, expectedHash)

Do you feel that these errors, combined with the InvalidArgument status code, are ambiguous any sense?

return nil, status.Error(codes.InvalidArgument, err.Error())
}

if err := msg.ValidateBasic(); err != nil {
return nil, err
// Check if the value already exists
if _, isFound := k.GetMorseAccountState(sdkCtx); isFound {
err := migrationtypes.ErrMorseAccountState.Wrap("already set")
logger.Info(err.Error())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here and elsewhere.

Not leaving any more comments so might be worth a ctrl+f for logger.Info(err.Error())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logger.Error() should ONLY be used for server-side errors (i.e. operator attention required), not client-side errors.

Node operators are the primary "user" (in a UX sense) of the log output. As such, any error which does not require operator attention SHOULD NOT use any level other than "debug"; however, due to reasons outlined in #856, we've decided to use the "info" level on-chain for both "info" and "debug" logs.

See: #1047 (comment)

return nil, status.Error(codes.FailedPrecondition, err.Error())
}

k.SetMorseAccountState(
ctx,
msg.MorseAccountState,
)
k.SetMorseAccountState(sdkCtx, msg.MorseAccountState)

// TODO_UPNEXT(@bryanchriswhite#1034): Emit an event...
stateHash, err := msg.MorseAccountState.GetHash()
bryanchriswhite marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
logger.Info(err.Error())
return nil, status.Error(codes.Internal, err.Error())
}

if err = sdkCtx.EventManager().EmitTypedEvent(
&migrationtypes.EventCreateMorseAccountState{
CreatedAtHeight: sdkCtx.BlockHeight(),
MorseAccountStateHash: stateHash,
NumAccounts: uint64(len(msg.MorseAccountState.Accounts)),
},
); err != nil {
logger.Info(err.Error())
return nil, err
}

// TODO_UPNEXT(@bryanchriswhite#1034): Populate the response...
return &types.MsgCreateMorseAccountStateResponse{}, nil
return &migrationtypes.MsgCreateMorseAccountStateResponse{
StateHash: stateHash,
NumAccounts: uint64(len(msg.MorseAccountState.Accounts)),
}, nil
}
Loading
Loading