feat(x/tx): implement SIGN_MODE_DIRECT_AUX handler (#15380)
Co-authored-by: Amaury <1293565+amaurym@users.noreply.github.com>
This commit is contained in:
parent
c254d0c608
commit
4a35885fb7
111
x/tx/signing/direct_aux/direct_aux.go
Normal file
111
x/tx/signing/direct_aux/direct_aux.go
Normal file
@ -0,0 +1,111 @@
|
||||
package direct_aux
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-proto/anyutil"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protodesc"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
|
||||
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
|
||||
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
|
||||
"cosmossdk.io/x/tx/signing"
|
||||
)
|
||||
|
||||
// SignModeHandler is the SIGN_MODE_DIRECT_AUX implementation of signing.SignModeHandler.
|
||||
type SignModeHandler struct {
|
||||
signersContext *signing.GetSignersContext
|
||||
fileResolver protodesc.Resolver
|
||||
typeResolver protoregistry.MessageTypeResolver
|
||||
}
|
||||
|
||||
// SignModeHandlerOptions are the options for the SignModeHandler.
|
||||
type SignModeHandlerOptions struct {
|
||||
// FileResolver is the protodesc.Resolver to use for resolving proto files when unpacking any messages.
|
||||
FileResolver protodesc.Resolver
|
||||
|
||||
// TypeResolver is the protoregistry.MessageTypeResolver to use for resolving proto types when unpacking any messages.
|
||||
TypeResolver protoregistry.MessageTypeResolver
|
||||
|
||||
// SignersContext is the signing.GetSignersContext to use for getting signers.
|
||||
SignersContext *signing.GetSignersContext
|
||||
}
|
||||
|
||||
// NewSignModeHandler returns a new SignModeHandler.
|
||||
func NewSignModeHandler(options SignModeHandlerOptions) SignModeHandler {
|
||||
h := SignModeHandler{}
|
||||
|
||||
if options.FileResolver == nil {
|
||||
h.fileResolver = protoregistry.GlobalFiles
|
||||
} else {
|
||||
h.fileResolver = options.FileResolver
|
||||
}
|
||||
|
||||
if options.TypeResolver == nil {
|
||||
h.typeResolver = protoregistry.GlobalTypes
|
||||
} else {
|
||||
h.typeResolver = options.TypeResolver
|
||||
}
|
||||
|
||||
if options.SignersContext == nil {
|
||||
h.signersContext = signing.NewGetSignersContext(signing.GetSignersOptions{ProtoFiles: h.fileResolver})
|
||||
} else {
|
||||
h.signersContext = options.SignersContext
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
var _ signing.SignModeHandler = SignModeHandler{}
|
||||
|
||||
// Mode implements signing.SignModeHandler.Mode.
|
||||
func (h SignModeHandler) Mode() signingv1beta1.SignMode {
|
||||
return signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX
|
||||
}
|
||||
|
||||
// getFirstSigner returns the first signer from the first message in the tx. It replicates behavior in
|
||||
// https://github.com/cosmos/cosmos-sdk/blob/4a6a1e3cb8de459891cb0495052589673d14ef51/x/auth/tx/builder.go#L142
|
||||
func (h SignModeHandler) getFirstSigner(txData signing.TxData) (string, error) {
|
||||
for _, anyMsg := range txData.Body.Messages {
|
||||
msg, err := anyutil.Unpack(anyMsg, h.fileResolver, h.typeResolver)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signer, err := h.signersContext.GetSigners(msg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return signer[0], nil
|
||||
}
|
||||
return "", fmt.Errorf("no signer found")
|
||||
}
|
||||
|
||||
// GetSignBytes implements signing.SignModeHandler.GetSignBytes.
|
||||
func (h SignModeHandler) GetSignBytes(
|
||||
_ context.Context, signerData signing.SignerData, txData signing.TxData) ([]byte, error) {
|
||||
|
||||
feePayer := txData.AuthInfo.Fee.Payer
|
||||
if feePayer == "" {
|
||||
fp, err := h.getFirstSigner(txData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
feePayer = fp
|
||||
}
|
||||
if feePayer == signerData.Address {
|
||||
return nil, fmt.Errorf("fee payer %s cannot sign with %s: unauthorized",
|
||||
feePayer, signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX)
|
||||
}
|
||||
|
||||
signDocDirectAux := &txv1beta1.SignDocDirectAux{
|
||||
BodyBytes: txData.BodyBytes,
|
||||
PublicKey: signerData.PubKey,
|
||||
ChainId: signerData.ChainId,
|
||||
AccountNumber: signerData.AccountNumber,
|
||||
Sequence: signerData.Sequence,
|
||||
Tip: txData.AuthInfo.Tip,
|
||||
}
|
||||
return proto.Marshal(signDocDirectAux)
|
||||
}
|
||||
144
x/tx/signing/direct_aux/direct_aux_test.go
Normal file
144
x/tx/signing/direct_aux/direct_aux_test.go
Normal file
@ -0,0 +1,144 @@
|
||||
package direct_aux_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-proto/anyutil"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
|
||||
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
"cosmossdk.io/api/cosmos/crypto/secp256k1"
|
||||
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
|
||||
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
|
||||
"cosmossdk.io/x/tx/signing"
|
||||
"cosmossdk.io/x/tx/signing/direct_aux"
|
||||
)
|
||||
|
||||
func TestDirectAuxHandler(t *testing.T) {
|
||||
feePayerAddr := "feePayer"
|
||||
chainID := "test-chain"
|
||||
memo := "sometestmemo"
|
||||
msg, err := anyutil.New(&bankv1beta1.MsgSend{})
|
||||
require.NoError(t, err)
|
||||
accNum, accSeq := uint64(1), uint64(2) // Arbitrary account number/sequence
|
||||
|
||||
pk := &secp256k1.PubKey{
|
||||
Key: make([]byte, 256),
|
||||
}
|
||||
anyPk, err := anyutil.New(pk)
|
||||
require.NoError(t, err)
|
||||
|
||||
signerInfo := []*txv1beta1.SignerInfo{
|
||||
{
|
||||
PublicKey: anyPk,
|
||||
ModeInfo: &txv1beta1.ModeInfo{
|
||||
Sum: &txv1beta1.ModeInfo_Single_{
|
||||
Single: &txv1beta1.ModeInfo_Single{
|
||||
Mode: signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX,
|
||||
},
|
||||
},
|
||||
},
|
||||
Sequence: accSeq,
|
||||
},
|
||||
}
|
||||
|
||||
fee := &txv1beta1.Fee{
|
||||
Amount: []*basev1beta1.Coin{{Denom: "uatom", Amount: "1000"}},
|
||||
GasLimit: 20000,
|
||||
Payer: feePayerAddr,
|
||||
}
|
||||
tip := &txv1beta1.Tip{Amount: []*basev1beta1.Coin{{Denom: "tip-token", Amount: "10"}}}
|
||||
|
||||
txBody := &txv1beta1.TxBody{
|
||||
Messages: []*anypb.Any{msg},
|
||||
Memo: memo,
|
||||
}
|
||||
|
||||
authInfo := &txv1beta1.AuthInfo{
|
||||
Fee: fee,
|
||||
Tip: tip,
|
||||
SignerInfos: signerInfo,
|
||||
}
|
||||
|
||||
signingData := signing.SignerData{
|
||||
ChainId: chainID,
|
||||
AccountNumber: accNum,
|
||||
Sequence: accSeq,
|
||||
Address: "",
|
||||
PubKey: anyPk,
|
||||
}
|
||||
|
||||
bodyBz, err := proto.Marshal(txBody)
|
||||
require.NoError(t, err)
|
||||
authInfoBz, err := proto.Marshal(authInfo)
|
||||
require.NoError(t, err)
|
||||
txData := signing.TxData{
|
||||
Body: txBody,
|
||||
AuthInfo: authInfo,
|
||||
AuthInfoBytes: authInfoBz,
|
||||
BodyBytes: bodyBz,
|
||||
}
|
||||
modeHandler := direct_aux.NewSignModeHandler(direct_aux.SignModeHandlerOptions{})
|
||||
|
||||
t.Log("verify fee payer cannot use SIGN_MODE_DIRECT_AUX")
|
||||
feePayerSigningData := signing.SignerData{
|
||||
ChainId: chainID,
|
||||
AccountNumber: accNum,
|
||||
Address: feePayerAddr,
|
||||
PubKey: anyPk,
|
||||
}
|
||||
_, err = modeHandler.GetSignBytes(context.Background(), feePayerSigningData, txData)
|
||||
require.EqualError(t, err, fmt.Sprintf("fee payer %s cannot sign with %s: unauthorized",
|
||||
feePayerAddr, signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX))
|
||||
|
||||
t.Log("verifying fee payer fallback to GetSigners cannot use SIGN_MODE_DIRECT_AUX")
|
||||
feeWithNoPayer := &txv1beta1.Fee{
|
||||
Amount: []*basev1beta1.Coin{{Denom: "uatom", Amount: "1000"}},
|
||||
GasLimit: 20000,
|
||||
}
|
||||
authInfoWithNoFeePayer := &txv1beta1.AuthInfo{
|
||||
Fee: feeWithNoPayer,
|
||||
Tip: tip,
|
||||
SignerInfos: signerInfo,
|
||||
}
|
||||
authInfoWithNoFeePayerBz, err := proto.Marshal(authInfoWithNoFeePayer)
|
||||
require.NoError(t, err)
|
||||
txDataWithNoFeePayer := signing.TxData{
|
||||
Body: txBody,
|
||||
BodyBytes: bodyBz,
|
||||
AuthInfo: authInfoWithNoFeePayer,
|
||||
AuthInfoBytes: authInfoWithNoFeePayerBz,
|
||||
}
|
||||
_, err = modeHandler.GetSignBytes(context.Background(), signingData, txDataWithNoFeePayer)
|
||||
require.EqualError(t, err, fmt.Sprintf("fee payer %s cannot sign with %s: unauthorized", "",
|
||||
signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX))
|
||||
|
||||
t.Log("verify GetSignBytes with generating sign bytes by marshaling signDocDirectAux")
|
||||
signBytes, err := modeHandler.GetSignBytes(context.Background(), signingData, txData)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, signBytes)
|
||||
|
||||
signDocDirectAux := &txv1beta1.SignDocDirectAux{
|
||||
BodyBytes: bodyBz,
|
||||
PublicKey: anyPk,
|
||||
ChainId: chainID,
|
||||
AccountNumber: accNum,
|
||||
Sequence: accSeq,
|
||||
Tip: tip,
|
||||
}
|
||||
expectedSignBytes, err := proto.Marshal(signDocDirectAux)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedSignBytes, signBytes)
|
||||
|
||||
t.Log("verify GetSignBytes with false txBody data")
|
||||
|
||||
signDocDirectAux.BodyBytes = []byte("dfafdasfds")
|
||||
expectedSignBytes, err = proto.Marshal(signDocDirectAux)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, expectedSignBytes, signBytes)
|
||||
}
|
||||
@ -3,11 +3,12 @@ package signing
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
msgv1 "cosmossdk.io/api/cosmos/msg/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protodesc"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
|
||||
msgv1 "cosmossdk.io/api/cosmos/msg/v1"
|
||||
)
|
||||
|
||||
// GetSignersContext is a context for retrieving the list of signers from a
|
||||
|
||||
Loading…
Reference in New Issue
Block a user