cosmos-sdk/tests/integration/tx/internal/util.go
2025-01-22 11:15:49 +00:00

222 lines
6.6 KiB
Go

package internal
import (
"bytes"
"context"
"encoding/json"
"testing"
gogoproto "github.com/cosmos/gogoproto/proto"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/dynamicpb"
apisigning "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
"cosmossdk.io/core/transaction"
"cosmossdk.io/x/tx/signing"
"cosmossdk.io/x/tx/signing/aminojson"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/std"
"github.com/cosmos/cosmos-sdk/tests/integration/tx/internal/pulsar/testpb"
"github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
)
var TestRepeatedFieldsSigner = signing.CustomGetSigner{
MsgType: proto.MessageName(&testpb.TestRepeatedFields{}),
Fn: func(msg proto.Message) ([][]byte, error) {
testMsg := msg.(*testpb.TestRepeatedFields)
// arbitrary logic
signer := testMsg.NullableDontOmitempty[1].Value
return [][]byte{[]byte(signer)}, nil
},
}
type noOpAddressCodec struct{}
func (a noOpAddressCodec) StringToBytes(text string) ([]byte, error) {
return []byte(text), nil
}
func (a noOpAddressCodec) BytesToString(bz []byte) (string, error) {
return string(bz), nil
}
type SigningFixture struct {
txConfig client.TxConfig
legacy *codec.LegacyAmino
protoCodec *codec.ProtoCodec
options SigningFixtureOptions
registry codectypes.InterfaceRegistry
}
type SigningFixtureOptions struct {
DoNotSortFields bool
}
func NewSigningFixture(
t *testing.T,
options SigningFixtureOptions,
modules ...module.AppModule,
) *SigningFixture {
t.Helper()
// set up transaction and signing infra
addressCodec, valAddressCodec := noOpAddressCodec{}, noOpAddressCodec{}
customGetSigners := []signing.CustomGetSigner{TestRepeatedFieldsSigner}
interfaceRegistry, _, err := codec.ProvideInterfaceRegistry(
addressCodec,
valAddressCodec,
customGetSigners,
)
require.NoError(t, err)
protoCodec := codec.ProvideProtoCodec(interfaceRegistry)
signingOptions := &signing.Options{
FileResolver: interfaceRegistry,
AddressCodec: addressCodec,
ValidatorAddressCodec: valAddressCodec,
}
for _, customGetSigner := range customGetSigners {
signingOptions.DefineCustomGetSigners(customGetSigner.MsgType, customGetSigner.Fn)
}
txConfig, err := tx.NewTxConfigWithOptions(
protoCodec,
tx.ConfigOptions{
EnabledSignModes: []apisigning.SignMode{
apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
},
SigningOptions: signingOptions,
})
require.NoError(t, err)
legacyAminoCodec := codec.NewLegacyAmino()
mb := module.NewManager(modules...)
std.RegisterLegacyAminoCodec(legacyAminoCodec)
std.RegisterInterfaces(interfaceRegistry)
mb.RegisterLegacyAminoCodec(legacyAminoCodec)
mb.RegisterInterfaces(interfaceRegistry)
return &SigningFixture{
txConfig: txConfig,
legacy: legacyAminoCodec,
options: options,
protoCodec: protoCodec,
registry: interfaceRegistry,
}
}
func (s *SigningFixture) RequireLegacyAminoEquivalent(t *testing.T, msg transaction.Msg) {
t.Helper()
// create tx envelope
txBuilder := s.txConfig.NewTxBuilder()
err := txBuilder.SetMsgs([]types.Msg{msg}...)
require.NoError(t, err)
builtTx := txBuilder.GetTx()
// round trip it to simulate application usage
txBz, err := s.txConfig.TxEncoder()(builtTx)
require.NoError(t, err)
theTx, err := s.txConfig.TxDecoder()(txBz)
require.NoError(t, err)
// create signing envelope
signerData := signing.SignerData{
Address: "sender-address",
ChainID: "test-chain",
AccountNumber: 0,
Sequence: 0,
}
adaptableTx, ok := theTx.(authsigning.V2AdaptableTx)
require.True(t, ok)
legacytx.RegressionTestingAminoCodec = s.legacy
defer func() {
legacytx.RegressionTestingAminoCodec = nil
}()
legacyAminoSignHandler := tx.NewSignModeLegacyAminoJSONHandler()
legacyBz, err := legacyAminoSignHandler.GetSignBytes(
apisigning.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
signing.SignerData{
ChainID: signerData.ChainID,
Address: signerData.Address,
AccountNumber: signerData.AccountNumber,
Sequence: signerData.Sequence,
},
theTx)
require.NoError(t, err)
handler := aminojson.NewSignModeHandler(aminojson.SignModeHandlerOptions{})
signBz, err := handler.GetSignBytes(
context.Background(),
signerData,
adaptableTx.GetSigningTxData(),
)
require.NoError(t, err)
require.Truef(t,
bytes.Equal(legacyBz, signBz),
"legacy: %s\n x/tx: %s", string(legacyBz), string(signBz))
}
func (s *SigningFixture) MarshalLegacyAminoJSON(t *testing.T, o any) []byte {
t.Helper()
bz, err := s.legacy.MarshalJSON(o)
require.NoError(t, err)
if s.options.DoNotSortFields {
return bz
}
sortedBz, err := sortJson(bz)
require.NoError(t, err)
return sortedBz
}
func (s *SigningFixture) UnmarshalGogoProto(bz []byte, ptr transaction.Msg) error {
return s.protoCodec.Unmarshal(bz, ptr)
}
func (s *SigningFixture) MessageDescriptor(t *testing.T, msg transaction.Msg) protoreflect.MessageDescriptor {
t.Helper()
typeName := gogoproto.MessageName(msg)
msgDesc, err := s.registry.FindDescriptorByName(protoreflect.FullName(typeName))
require.NoError(t, err)
return msgDesc.(protoreflect.MessageDescriptor)
}
// DynamicMessage is identical to the Decoder implementation in
// https://github.com/cosmos/cosmos-sdk/blob/6d2f6ff068c81c5783e01319beaa51c7dbb43edd/x/tx/decode/decode.go#L136
// It is duplicated here to test dynamic message implementations specifically.
// The code path linked above is also covered in this package.
func (s *SigningFixture) DynamicMessage(t *testing.T, msg transaction.Msg) proto.Message {
t.Helper()
msgDesc := s.MessageDescriptor(t, msg)
protoBz, err := gogoproto.Marshal(msg)
require.NoError(t, err)
dynamicMsg := dynamicpb.NewMessageType(msgDesc).New().Interface()
err = proto.Unmarshal(protoBz, dynamicMsg)
require.NoError(t, err)
return dynamicMsg
}
// sortJson sorts the JSON bytes by way of the side effect of unmarshalling and remarshalling
// the JSON using encoding/json. This hacky way of sorting JSON fields was used by the legacy
// amino JSON encoding x/auth/migrations/legacytx.StdSignBytes. It is used here ensure the x/tx
// JSON encoding is equivalent to the legacy amino JSON encoding.
func sortJson(bz []byte) ([]byte, error) {
var c any
err := json.Unmarshal(bz, &c)
if err != nil {
return nil, err
}
js, err := json.Marshal(c)
if err != nil {
return nil, err
}
return js, nil
}