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

feat(accounts/base): give chains the possibility to pick their chosen pubkey types for the base accounts #21466

Merged
merged 8 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
1,326 changes: 1,123 additions & 203 deletions api/cosmos/accounts/defaults/base/v1/base.pulsar.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion scripts/protocgen.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ for dir in $proto_dirs; do

# check if buf.gen.gogo.yaml exists in the proto directory
if [ -f "buf.gen.gogo.yaml" ]; then
for file in $(find . -maxdepth 5 -name '*.proto'); do
for file in $(find . -maxdepth 8 -name '*.proto'); do
Copy link
Contributor

Choose a reason for hiding this comment

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

Use find -exec to address the Shellcheck warning.

For loops over find output are fragile. Use find -exec to address the Shellcheck warning.

Apply this diff to fix the issue:

-      for file in $(find . -maxdepth 8 -name '*.proto'); do
+      find . -maxdepth 8 -name '*.proto' -exec sh -c '
+        for file; do
+          if grep -q "option go_package" "$file" && grep -H -o -c "option go_package.*cosmossdk.io/api" "$file" | grep -q ":0$"; then
+            buf generate --template buf.gen.gogo.yaml "$file"
+          fi
+        done
+      ' sh {} +
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for file in $(find . -maxdepth 8 -name '*.proto'); do
find . -maxdepth 8 -name '*.proto' -exec sh -c '
for file; do
if grep -q "option go_package" "$file" && grep -H -o -c "option go_package.*cosmossdk.io/api" "$file" | grep -q ":0$"; then
buf generate --template buf.gen.gogo.yaml "$file"
fi
done
' sh {} +
Tools
Shellcheck

[warning] 32-32: For loops over find output are fragile. Use find -exec or a while read loop.

(SC2044)

# this regex checks if a proto file has its go_package set to cosmossdk.io/api/...
# gogo proto files SHOULD ONLY be generated if this is false
# we don't want gogo proto to run for proto files which are natively built for google.golang.org/protobuf
Expand Down
2 changes: 1 addition & 1 deletion simapp/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Always refer to the [UPGRADING.md](https://github.com/cosmos/cosmos-sdk/blob/mai
* [#20771](https://github.com/cosmos/cosmos-sdk/pull/20771) Use client/v2 `GetNodeHomeDirectory` helper in `app.go` and use the `DefaultNodeHome` constant everywhere in the app.
* [#20490](https://github.com/cosmos/cosmos-sdk/pull/20490) Refactor simulations to make use of `testutil/sims` instead of `runsims`.
* [#19726](https://github.com/cosmos/cosmos-sdk/pull/19726) Update APIs to match CometBFT v1.

* [#21466](https://github.com/cosmos/cosmos-sdk/pull/21466) Allow chains to plug in their own public key types in `base.Account`
<!-- TODO: move changelog.md elements to here -->

## v0.47 to v0.50
Expand Down
2 changes: 1 addition & 1 deletion simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ func NewSimApp(
accountstd.AddAccount(lockup.DELAYED_LOCKING_ACCOUNT, lockup.NewDelayedLockingAccount),
accountstd.AddAccount(lockup.PERMANENT_LOCKING_ACCOUNT, lockup.NewPermanentLockingAccount),
// PRODUCTION: add
baseaccount.NewAccount("base", txConfig.SignModeHandler()),
baseaccount.NewAccount("base", txConfig.SignModeHandler(), baseaccount.WithSecp256K1PubKey()),
)
if err != nil {
panic(err)
Expand Down
28 changes: 19 additions & 9 deletions store/internal/kv/kv.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/integration/auth/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func initFixture(t *testing.T) *fixture {
queryRouter := baseapp.NewGRPCQueryRouter()

handler := directHandler{}
account := baseaccount.NewAccount("base", signing.NewHandlerMap(handler))
account := baseaccount.NewAccount("base", signing.NewHandlerMap(handler), baseaccount.WithSecp256K1PubKey())
accountsKeeper, err := accounts.NewKeeper(
cdc,
runtime.NewEnvironment(runtime.NewKVStoreService(keys[accounts.StoreKey]), log.NewNopLogger(), runtime.EnvWithQueryRouterService(queryRouter), runtime.EnvWithMsgRouterService(router)),
Expand Down
123 changes: 88 additions & 35 deletions x/accounts/defaults/base/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"

dcrd_secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"

Expand All @@ -20,58 +19,64 @@ import (
accountsv1 "cosmossdk.io/x/accounts/v1"
"cosmossdk.io/x/tx/signing"

"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/types/tx"
)

var (
PubKeyPrefix = collections.NewPrefix(0)
SequencePrefix = collections.NewPrefix(1)
PubKeyPrefix = collections.NewPrefix(0)
PubKeyTypePrefix = collections.NewPrefix(1)
SequencePrefix = collections.NewPrefix(2)
)

func NewAccount(name string, handlerMap *signing.HandlerMap) accountstd.AccountCreatorFunc {
type Option func(a *Account)

func NewAccount(name string, handlerMap *signing.HandlerMap, options ...Option) accountstd.AccountCreatorFunc {
return func(deps accountstd.Dependencies) (string, accountstd.Interface, error) {
return name, Account{
PubKey: collections.NewItem(deps.SchemaBuilder, PubKeyPrefix, "pub_key", codec.CollValue[secp256k1.PubKey](deps.LegacyStateCodec)),
Sequence: collections.NewSequence(deps.SchemaBuilder, SequencePrefix, "sequence"),
addrCodec: deps.AddressCodec,
signingHandlers: handlerMap,
hs: deps.Environment.HeaderService,
}, nil
acc := Account{
PubKey: collections.NewItem(deps.SchemaBuilder, PubKeyPrefix, "pub_key_bytes", collections.BytesValue),
PubKeyType: collections.NewItem(deps.SchemaBuilder, PubKeyTypePrefix, "pub_key_type", collections.StringValue),
Sequence: collections.NewSequence(deps.SchemaBuilder, SequencePrefix, "sequence"),
addrCodec: deps.AddressCodec,
hs: deps.Environment.HeaderService,
supportedPubKeys: map[string]pubKeyImpl{},
signingHandlers: handlerMap,
}
for _, option := range options {
option(&acc)
}
if len(acc.supportedPubKeys) == 0 {
return "", nil, fmt.Errorf("no public keys plugged for account type %s", name)
}
return name, acc, nil
}
}

// Account implements a base account.
type Account struct {
PubKey collections.Item[secp256k1.PubKey]
PubKey collections.Item[[]byte]
PubKeyType collections.Item[string]

Sequence collections.Sequence

addrCodec address.Codec
hs header.Service

supportedPubKeys map[string]pubKeyImpl

signingHandlers *signing.HandlerMap
}

func (a Account) Init(ctx context.Context, msg *v1.MsgInit) (*v1.MsgInitResponse, error) {
return &v1.MsgInitResponse{}, a.verifyAndSetPubKey(ctx, msg.PubKey)
return &v1.MsgInitResponse{}, a.savePubKey(ctx, msg.PubKey)
}

func (a Account) SwapPubKey(ctx context.Context, msg *v1.MsgSwapPubKey) (*v1.MsgSwapPubKeyResponse, error) {
if !accountstd.SenderIsSelf(ctx) {
return nil, errors.New("unauthorized")
}

return &v1.MsgSwapPubKeyResponse{}, a.verifyAndSetPubKey(ctx, msg.NewPubKey)
}

func (a Account) verifyAndSetPubKey(ctx context.Context, key []byte) error {
_, err := dcrd_secp256k1.ParsePubKey(key)
if err != nil {
return err
}
return a.PubKey.Set(ctx, secp256k1.PubKey{Key: key})
return &v1.MsgSwapPubKeyResponse{}, a.savePubKey(ctx, msg.NewPubKey)
}

// Authenticate implements the authentication flow of an abstracted base account.
Expand All @@ -82,12 +87,12 @@ func (a Account) Authenticate(ctx context.Context, msg *aa_interface_v1.MsgAuthe

pubKey, signerData, err := a.computeSignerData(ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to compute signer data: %w", err)
}

txData, err := a.getTxData(msg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to get tx data: %w", err)
}

gotSeq := msg.Tx.AuthInfo.SignerInfos[msg.SignerIndex].Sequence
Expand All @@ -104,7 +109,7 @@ func (a Account) Authenticate(ctx context.Context, msg *aa_interface_v1.MsgAuthe

signBytes, err := a.signingHandlers.GetSignBytes(ctx, signMode, signerData, txData)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to get sign bytes: %w", err)
}

if !pubKey.VerifySignature(signBytes, signature) {
Expand All @@ -123,31 +128,31 @@ func parseSignMode(info *tx.ModeInfo) (signingv1beta1.SignMode, error) {
}

// computeSignerData will populate signer data and also increase the sequence.
func (a Account) computeSignerData(ctx context.Context) (secp256k1.PubKey, signing.SignerData, error) {
func (a Account) computeSignerData(ctx context.Context) (PubKey, signing.SignerData, error) {
addrStr, err := a.addrCodec.BytesToString(accountstd.Whoami(ctx))
if err != nil {
return secp256k1.PubKey{}, signing.SignerData{}, err
return nil, signing.SignerData{}, err
}
chainID := a.hs.HeaderInfo(ctx).ChainID

wantSequence, err := a.Sequence.Next(ctx)
if err != nil {
return secp256k1.PubKey{}, signing.SignerData{}, err
return nil, signing.SignerData{}, err
}

pk, err := a.PubKey.Get(ctx)
pk, err := a.loadPubKey(ctx)
if err != nil {
return secp256k1.PubKey{}, signing.SignerData{}, err
return nil, signing.SignerData{}, err
}

pkAny, err := codectypes.NewAnyWithValue(&pk)
pkAny, err := codectypes.NewAnyWithValue(pk)
if err != nil {
return secp256k1.PubKey{}, signing.SignerData{}, err
return nil, signing.SignerData{}, err
}

accNum, err := a.getNumber(ctx, addrStr)
if err != nil {
return secp256k1.PubKey{}, signing.SignerData{}, err
return nil, signing.SignerData{}, err
}

return pk, signing.SignerData{
Expand Down Expand Up @@ -200,6 +205,54 @@ func (a Account) getTxData(msg *aa_interface_v1.MsgAuthenticate) (signing.TxData
}, nil
}

func (a Account) loadPubKey(ctx context.Context) (PubKey, error) {
pkType, err := a.PubKeyType.Get(ctx)
if err != nil {
return nil, err
}

publicKey, exists := a.supportedPubKeys[pkType]
// this means that the chain developer suddenly started using a key type.
if !exists {
return nil, fmt.Errorf("pubkey type %s is not supported by the chain anymore", pkType)
}

pkBytes, err := a.PubKey.Get(ctx)
if err != nil {
return nil, err
}

pubKey, err := publicKey.decode(pkBytes)
if err != nil {
return nil, err
}
return pubKey, nil
}

func (a Account) savePubKey(ctx context.Context, anyPk *codectypes.Any) error {
// check if known
name := nameFromTypeURL(anyPk.TypeUrl)
impl, exists := a.supportedPubKeys[name]
if !exists {
return fmt.Errorf("unknown pubkey type %s", name)
}
pk, err := impl.decode(anyPk.Value)
if err != nil {
return fmt.Errorf("unable to decode pubkey: %w", err)
}
err = impl.validate(pk)
if err != nil {
return fmt.Errorf("unable to validate pubkey: %w", err)
}

// save into state
err = a.PubKey.Set(ctx, anyPk.Value)
if err != nil {
return fmt.Errorf("unable to save pubkey: %w", err)
}
return a.PubKeyType.Set(ctx, name)
}

func (a Account) QuerySequence(ctx context.Context, _ *v1.QuerySequence) (*v1.QuerySequenceResponse, error) {
seq, err := a.Sequence.Peek(ctx)
if err != nil {
Expand Down
Loading
Loading