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(client/v2): factory and txBuilder #20623

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d4d2320
first approach
JulianToledano Jun 3, 2024
2a777df
TxBuilder implemented
JulianToledano Jun 5, 2024
ffe66ac
add: txBuilder tests
JulianToledano Jun 6, 2024
3cbcdcb
txconfig
JulianToledano Jun 7, 2024
2ff6edd
factory works
JulianToledano Jun 11, 2024
5fec5af
update: Decoder interface,
JulianToledano Jun 12, 2024
6eb3c98
update: factory
JulianToledano Jun 17, 2024
deb9fa3
Merge branch 'main' into julian/v2-tx-builder
JulianToledano Jun 17, 2024
f3071a4
add: factory tests
JulianToledano Jun 18, 2024
a62bc64
Merge branch 'main' into julian/v2-tx-builder
JulianToledano Jun 18, 2024
b9bcaa6
del: most sdk dependency
JulianToledano Jun 19, 2024
1270a87
fix: dry-run and sign
JulianToledano Jun 19, 2024
2e4895f
update: simplify accountconfig setter
JulianToledano Jun 19, 2024
51ffeb2
fix: dry-run offline
JulianToledano Jun 20, 2024
189e6b3
add: godoc
JulianToledano Jun 20, 2024
4fadeff
lint
JulianToledano Jun 20, 2024
d49ce6f
Merge branch 'main' into julian/v2-tx-builder
JulianToledano Jun 20, 2024
c2b2539
add: SignaturesMarshal
JulianToledano Jun 21, 2024
1a29aa8
lint
JulianToledano Jun 21, 2024
5854380
remove todo
JulianToledano Jun 21, 2024
4094945
fix: go-mod-tidy-all
JulianToledano Jun 21, 2024
9ee9940
update: coderrabbit suggestions
JulianToledano Jun 28, 2024
cd55292
fix: signingTxData encoding
JulianToledano Jul 5, 2024
3dcc970
add: DecodedTx wrapper
JulianToledano Jul 5, 2024
4b55c93
update: encoder decoder
JulianToledano Jul 8, 2024
3db0bab
lint :)
JulianToledano Jul 8, 2024
87677d7
add: readme with diagrams
JulianToledano Jul 8, 2024
d360670
lint
JulianToledano Jul 8, 2024
5a8aba9
update: buildSignedTx
JulianToledano Jul 17, 2024
1e3e29f
Merge branch 'main' into julian/v2-tx-builder
JulianToledano Aug 15, 2024
7acdd15
update: use timeoutTimestamp,
JulianToledano Aug 20, 2024
a0dc147
update: prepare is now only used in constructor
JulianToledano Aug 20, 2024
78bb01e
update: remove generateOnly from params
JulianToledano Aug 21, 2024
1c76296
update: remove offchain from params
JulianToledano Aug 21, 2024
4c8ee9b
update: remove offline from params
JulianToledano Aug 21, 2024
d849302
del: remove default gas
JulianToledano Aug 21, 2024
7ffed78
lint
JulianToledano Aug 21, 2024
738605d
Merge branch 'main' into julian/v2-tx-builder
JulianToledano Sep 17, 2024
4a51df0
avoid flags2
JulianToledano Sep 19, 2024
9153602
lint-happy
JulianToledano Sep 19, 2024
9547499
tidy
JulianToledano Sep 19, 2024
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
8 changes: 4 additions & 4 deletions client/v2/autocli/flag/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (a addressValue) String() string {
// Set implements the flag.Value interface for addressValue.
func (a *addressValue) Set(s string) error {
// we get the keyring on set, as in NewValue the context is the parent context (before RunE)
keyring := getKeyringFromCtx(a.ctx)
keyring := getKeyringFromCtx(a.ctx, a.addressCodec)
addr, err := keyring.LookupAddressByKeyName(s)
if err == nil {
addrStr, err := a.addressCodec.BytesToString(addr)
Expand Down Expand Up @@ -110,7 +110,7 @@ func (a consensusAddressValue) String() string {

func (a *consensusAddressValue) Set(s string) error {
// we get the keyring on set, as in NewValue the context is the parent context (before RunE)
keyring := getKeyringFromCtx(a.ctx)
keyring := getKeyringFromCtx(a.ctx, a.addressCodec)
addr, err := keyring.LookupAddressByKeyName(s)
if err == nil {
addrStr, err := a.addressCodec.BytesToString(addr)
Expand Down Expand Up @@ -147,11 +147,11 @@ func (a *consensusAddressValue) Set(s string) error {
return nil
}

func getKeyringFromCtx(ctx *context.Context) keyring.Keyring {
func getKeyringFromCtx(ctx *context.Context, ac address.Codec) keyring.Keyring {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure but isn't address.Codec available inside Context ?

dctx := *ctx
if dctx != nil {
if clientCtx := dctx.Value(client.ClientContextKey); clientCtx != nil {
k, err := sdkkeyring.NewAutoCLIKeyring(clientCtx.(*client.Context).Keyring)
k, err := sdkkeyring.NewAutoCLIKeyring(clientCtx.(*client.Context).Keyring, ac)
if err != nil {
panic(fmt.Errorf("failed to create keyring: %w", err))
}
Expand Down
6 changes: 6 additions & 0 deletions client/v2/autocli/keyring/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,10 @@ type Keyring interface {

// Sign signs the given bytes with the key with the given name.
Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error)

// KeyType returns the type of the key.
KeyType(name string) (uint, error)

// KeyInfo given a key name or address returns key name, key address and key type.
KeyInfo(nameOrAddr string) (string, string, uint, error)
}
10 changes: 10 additions & 0 deletions client/v2/autocli/keyring/keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,13 @@ func (k *KeyringImpl) LookupAddressByKeyName(name string) ([]byte, error) {
func (k *KeyringImpl) Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) {
return k.k.Sign(name, msg, signMode)
}

// KeyType returns the type of the key.
func (k *KeyringImpl) KeyType(name string) (uint, error) {
return k.k.KeyType(name)
}

// KeyInfo given a key name or address returns key name, key address and key type.
func (k *KeyringImpl) KeyInfo(nameOrAddr string) (string, string, uint, error) {
return k.k.KeyInfo(nameOrAddr)
}
8 changes: 8 additions & 0 deletions client/v2/autocli/keyring/no_keyring.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,11 @@ func (k NoKeyring) GetPubKey(name string) (cryptotypes.PubKey, error) {
func (k NoKeyring) Sign(name string, msg []byte, signMode signingv1beta1.SignMode) ([]byte, error) {
return nil, errNoKeyring
}

func (k NoKeyring) KeyType(name string) (uint, error) {
return 0, errNoKeyring
}

func (k NoKeyring) KeyInfo(name string) (string, string, uint, error) {
return "", "", 0, errNoKeyring
}
2 changes: 1 addition & 1 deletion client/v2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ require (
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v1.0.2 // indirect
github.com/cosmos/crypto v0.0.0-20240309083813-82ed2537802e // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/gogoproto v1.5.0
github.com/cosmos/iavl v1.2.0 // indirect
Expand Down
116 changes: 116 additions & 0 deletions client/v2/internal/account/retriever.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package account

import (
"context"
"fmt"
"strconv"

gogogrpc "github.com/cosmos/gogoproto/grpc"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"

"cosmossdk.io/core/address"
authtypes "cosmossdk.io/x/auth/types"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// GRPCBlockHeightHeader represents the gRPC header for block height.
const GRPCBlockHeightHeader = "x-cosmos-block-height"

var _ AccountRetriever = accountRetriever{}

// Account provides a read-only abstraction over the auth module's AccountI.
type Account interface {
GetAddress() sdk.AccAddress
GetPubKey() cryptotypes.PubKey // can return nil.
GetAccountNumber() uint64
GetSequence() uint64
}

// AccountRetriever defines methods required to retrieve account details necessary for transaction signing.
type AccountRetriever interface {
GetAccount(context.Context, []byte) (Account, error)
GetAccountWithHeight(context.Context, []byte) (Account, int64, error)
EnsureExists(context.Context, []byte) error
GetAccountNumberSequence(context.Context, []byte) (accNum, accSeq uint64, err error)
}

type accountRetriever struct {
ac address.Codec
conn gogogrpc.ClientConn
registry codectypes.InterfaceRegistry
}

// NewAccountRetriever creates a new instance of accountRetriever.
func NewAccountRetriever(ac address.Codec, conn gogogrpc.ClientConn, registry codectypes.InterfaceRegistry) *accountRetriever {
return &accountRetriever{
ac: ac,
conn: conn,
registry: registry,
}
}

// GetAccount retrieves an account using its address.
func (a accountRetriever) GetAccount(ctx context.Context, addr []byte) (Account, error) {
acc, _, err := a.GetAccountWithHeight(ctx, addr)
return acc, err
}

// GetAccountWithHeight retrieves an account and its associated block height using the account's address.
func (a accountRetriever) GetAccountWithHeight(ctx context.Context, addr []byte) (Account, int64, error) {
var header metadata.MD
qc := authtypes.NewQueryClient(a.conn)

addrStr, err := a.ac.BytesToString(addr)
if err != nil {
return nil, 0, err
}

res, err := qc.Account(ctx, &authtypes.QueryAccountRequest{Address: addrStr}, grpc.Header(&header))
if err != nil {
return nil, 0, err
}

blockHeight := header.Get(GRPCBlockHeightHeader)
if len(blockHeight) != 1 {
return nil, 0, fmt.Errorf("unexpected '%s' header length; got %d, expected 1", GRPCBlockHeightHeader, len(blockHeight))
}

nBlockHeight, err := strconv.Atoi(blockHeight[0])
if err != nil {
return nil, 0, fmt.Errorf("failed to parse block height: %w", err)
}

var acc Account
if err := a.registry.UnpackAny(res.Account, &acc); err != nil {
return nil, 0, err
}

return acc, int64(nBlockHeight), nil
}
JulianToledano marked this conversation as resolved.
Show resolved Hide resolved

// EnsureExists checks if an account exists using its address.
func (a accountRetriever) EnsureExists(ctx context.Context, addr []byte) error {
if _, err := a.GetAccount(ctx, addr); err != nil {
return err
}
return nil
}

// GetAccountNumberSequence retrieves the account number and sequence for an account using its address.
func (a accountRetriever) GetAccountNumberSequence(ctx context.Context, addr []byte) (accNum, accSeq uint64, err error) {
acc, err := a.GetAccount(ctx, addr)
if err != nil {
if status.Code(err) == codes.NotFound {
return 0, 0, nil
}
return 0, 0, err
}

return acc.GetAccountNumber(), acc.GetSequence(), nil
}
66 changes: 66 additions & 0 deletions client/v2/internal/coins/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package coins

import (
"errors"

base "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/math"

sdk "github.com/cosmos/cosmos-sdk/types"
)

var (
_ withAmount = &base.Coin{}
_ withAmount = &base.DecCoin{}
)

type withAmount interface {
GetAmount() string
}

// IsZero check if given coins are zero.
func IsZero[T withAmount](coins []T) (bool, error) {
for _, coin := range coins {
amount, ok := math.NewIntFromString(coin.GetAmount())
if !ok {
return false, errors.New("invalid coin amount")
}
if !amount.IsZero() {
return false, nil
}
}
return true, nil
}

func ParseDecCoins(coins string) ([]*base.DecCoin, error) {
parsedGasPrices, err := sdk.ParseDecCoins(coins) // TODO: do it here to avoid sdk dependency
if err != nil {
return nil, err
}

finalGasPrices := make([]*base.DecCoin, len(parsedGasPrices))
for i, coin := range parsedGasPrices {
finalGasPrices[i] = &base.DecCoin{
Denom: coin.Denom,
Amount: coin.Amount.String(),
}
}
return finalGasPrices, nil
}
JulianToledano marked this conversation as resolved.
Show resolved Hide resolved

func ParseCoinsNormalized(coins string) ([]*base.Coin, error) {
parsedFees, err := sdk.ParseCoinsNormalized(coins) // TODO: do it here to avoid sdk dependency
if err != nil {
return nil, err
}

finalFees := make([]*base.Coin, len(parsedFees))
for i, coin := range parsedFees {
finalFees[i] = &base.Coin{
Denom: coin.Denom,
Amount: coin.Amount.String(),
}
}

return finalFees, nil
}
JulianToledano marked this conversation as resolved.
Show resolved Hide resolved
83 changes: 83 additions & 0 deletions client/v2/internal/coins/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package coins

import (
"testing"

"github.com/stretchr/testify/require"

base "cosmossdk.io/api/cosmos/base/v1beta1"
)

func TestCoinIsZero(t *testing.T) {
type testCase[T withAmount] struct {
name string
coins []T
isZero bool
}
tests := []testCase[*base.Coin]{
{
name: "not zero coin",
coins: []*base.Coin{
{
Denom: "stake",
Amount: "100",
},
},
isZero: false,
},
{
name: "zero coin",
coins: []*base.Coin{
{
Denom: "stake",
Amount: "0",
},
},
isZero: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := IsZero(tt.coins)
require.NoError(t, err)
require.Equal(t, got, tt.isZero)
})
}
}

func TestDecCoinIsZero(t *testing.T) {
type testCase[T withAmount] struct {
name string
coins []T
isZero bool
}
tests := []testCase[*base.DecCoin]{
{
name: "not zero coin",
coins: []*base.DecCoin{
{
Denom: "stake",
Amount: "100",
},
},
isZero: false,
},
{
name: "zero coin",
coins: []*base.DecCoin{
{
Denom: "stake",
Amount: "0",
},
},
isZero: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := IsZero(tt.coins)
require.NoError(t, err)
require.Equal(t, got, tt.isZero)
})
}
}
21 changes: 21 additions & 0 deletions client/v2/internal/grpc/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package grpc

import (
"context"
"fmt"

gogogrpc "github.com/cosmos/gogoproto/grpc"
"google.golang.org/grpc"
)

var _ gogogrpc.ClientConn = ClientConn{}

type ClientConn struct{}

func (c ClientConn) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error {
panic("implement me")
JulianToledano marked this conversation as resolved.
Show resolved Hide resolved
}
JulianToledano marked this conversation as resolved.
Show resolved Hide resolved

func (c ClientConn) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
return nil, fmt.Errorf("streaming rpc not supported")
JulianToledano marked this conversation as resolved.
Show resolved Hide resolved
}
JulianToledano marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion client/v2/offchain/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func Sign(ctx client.Context, rawBytes []byte, fromName, indent, encoding, outpu

// sign signs a digest with provided key and SignMode.
func sign(ctx client.Context, fromName, digest string) (*apitx.Tx, error) {
keybase, err := keyring.NewAutoCLIKeyring(ctx.Keyring)
keybase, err := keyring.NewAutoCLIKeyring(ctx.Keyring, ctx.AddressCodec)
if err != nil {
return nil, err
}
Expand Down
Loading
Loading