cosmos-sdk/client/v2/offchain/builder.go
son trinh 585335690b
feat(tx)!: make timeout_height time based (#20870)
Co-authored-by: Marko <marko@baricevic.me>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-07-29 11:53:27 +00:00

319 lines
7.8 KiB
Go

package offchain
// TODO: remove custom off-chain builder once v2 tx builder is developed.
import (
"errors"
"fmt"
"github.com/cosmos/cosmos-proto/anyutil"
"github.com/cosmos/gogoproto/proto"
protov2 "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
apitx "cosmossdk.io/api/cosmos/tx/v1beta1"
txsigning "cosmossdk.io/x/tx/signing"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
)
type builder struct {
cdc codec.Codec
tx *apitx.Tx
}
func newBuilder(cdc codec.Codec) *builder {
return &builder{
cdc: cdc,
tx: &apitx.Tx{
Body: &apitx.TxBody{},
AuthInfo: &apitx.AuthInfo{
Fee: &apitx.Fee{
Amount: nil,
GasLimit: 0,
Payer: "",
Granter: "",
},
},
Signatures: nil,
},
}
}
// GetTx returns the tx.
func (b *builder) GetTx() *apitx.Tx {
return b.tx
}
// GetSigningTxData returns the necessary data to generate sign bytes.
func (b *builder) GetSigningTxData() (txsigning.TxData, error) {
body := b.tx.Body
authInfo := b.tx.AuthInfo
msgs := make([]*anypb.Any, len(body.Messages))
for i, msg := range body.Messages {
msgs[i] = &anypb.Any{
TypeUrl: msg.TypeUrl,
Value: msg.Value,
}
}
extOptions := make([]*anypb.Any, len(body.ExtensionOptions))
for i, extOption := range body.ExtensionOptions {
extOptions[i] = &anypb.Any{
TypeUrl: extOption.TypeUrl,
Value: extOption.Value,
}
}
nonCriticalExtOptions := make([]*anypb.Any, len(body.NonCriticalExtensionOptions))
for i, extOption := range body.NonCriticalExtensionOptions {
nonCriticalExtOptions[i] = &anypb.Any{
TypeUrl: extOption.TypeUrl,
Value: extOption.Value,
}
}
feeCoins := authInfo.Fee.Amount
feeAmount := make([]*basev1beta1.Coin, len(feeCoins))
for i, coin := range feeCoins {
feeAmount[i] = &basev1beta1.Coin{
Denom: coin.Denom,
Amount: coin.Amount,
}
}
txSignerInfos := make([]*apitx.SignerInfo, len(authInfo.SignerInfos))
for i, signerInfo := range authInfo.SignerInfos {
txSignerInfo := &apitx.SignerInfo{
PublicKey: &anypb.Any{
TypeUrl: signerInfo.PublicKey.TypeUrl,
Value: signerInfo.PublicKey.Value,
},
Sequence: signerInfo.Sequence,
ModeInfo: signerInfo.ModeInfo,
}
txSignerInfos[i] = txSignerInfo
}
txAuthInfo := &apitx.AuthInfo{
SignerInfos: txSignerInfos,
Fee: &apitx.Fee{
Amount: feeAmount,
GasLimit: authInfo.Fee.GasLimit,
Payer: authInfo.Fee.Payer,
Granter: authInfo.Fee.Granter,
},
}
txBody := &apitx.TxBody{
Messages: msgs,
Memo: body.Memo,
TimeoutHeight: body.TimeoutHeight,
TimeoutTimestamp: body.TimeoutTimestamp,
ExtensionOptions: extOptions,
NonCriticalExtensionOptions: nonCriticalExtOptions,
}
authInfoBz, err := protov2.Marshal(b.tx.AuthInfo)
if err != nil {
return txsigning.TxData{}, err
}
bodyBz, err := protov2.Marshal(b.tx.Body)
if err != nil {
return txsigning.TxData{}, err
}
txData := txsigning.TxData{
AuthInfo: txAuthInfo,
AuthInfoBytes: authInfoBz,
Body: txBody,
BodyBytes: bodyBz,
}
return txData, nil
}
// GetPubKeys returns the pubKeys of the tx.
func (b *builder) GetPubKeys() ([]cryptotypes.PubKey, error) { // If signer already has pubkey in context, this list will have nil in its place
signerInfos := b.tx.AuthInfo.SignerInfos
pks := make([]cryptotypes.PubKey, len(signerInfos))
for i, si := range signerInfos {
// NOTE: it is okay to leave this nil if there is no PubKey in the SignerInfo.
// PubKey's can be left unset in SignerInfo.
if si.PublicKey == nil {
continue
}
var pk cryptotypes.PubKey
anyPk := &codectypes.Any{
TypeUrl: si.PublicKey.TypeUrl,
Value: si.PublicKey.Value,
}
err := b.cdc.UnpackAny(anyPk, &pk)
if err != nil {
return nil, err
}
pks[i] = pk
}
return pks, nil
}
// GetSignatures returns the signatures of the tx.
func (b *builder) GetSignatures() ([]OffchainSignature, error) {
signerInfos := b.tx.AuthInfo.SignerInfos
sigs := b.tx.Signatures
pubKeys, err := b.GetPubKeys()
if err != nil {
return nil, err
}
n := len(signerInfos)
res := make([]OffchainSignature, n)
for i, si := range signerInfos {
// handle nil signatures (in case of simulation)
if si.ModeInfo == nil {
res[i] = OffchainSignature{
PubKey: pubKeys[i],
}
} else {
var err error
sigData, err := modeInfoAndSigToSignatureData(si.ModeInfo, sigs[i])
if err != nil {
return nil, err
}
// sequence number is functionally a transaction nonce and referred to as such in the SDK
nonce := si.GetSequence()
res[i] = OffchainSignature{
PubKey: pubKeys[i],
Data: sigData,
Sequence: nonce,
}
}
}
return res, nil
}
// GetSigners returns the signers of the tx.
func (b *builder) GetSigners() ([][]byte, error) {
signers, _, err := b.getSigners()
return signers, err
}
func (b *builder) getSigners() ([][]byte, []protov2.Message, error) {
var signers [][]byte
seen := map[string]bool{}
var msgsv2 []protov2.Message
for _, msg := range b.tx.Body.Messages {
msgv2, err := anyutil.Unpack(msg, b.cdc.InterfaceRegistry(), nil)
if err != nil {
return nil, nil, err
}
xs, err := b.cdc.InterfaceRegistry().SigningContext().GetSigners(msgv2)
if err != nil {
return nil, nil, err
}
msgsv2 = append(msgsv2, msg)
for _, signer := range xs {
if !seen[string(signer)] {
signers = append(signers, signer)
seen[string(signer)] = true
}
}
}
return signers, msgsv2, nil
}
func (b *builder) setMsgs(msgs ...proto.Message) error {
anys := make([]*anypb.Any, len(msgs))
for i, msg := range msgs {
protoMsg, ok := msg.(protov2.Message)
if !ok {
return errors.New("message is not a proto.Message")
}
protov2MarshalOpts := protov2.MarshalOptions{Deterministic: true}
bz, err := protov2MarshalOpts.Marshal(protoMsg)
if err != nil {
return err
}
anys[i] = &anypb.Any{
TypeUrl: codectypes.MsgTypeURL(msg),
Value: bz,
}
}
b.tx.Body.Messages = anys
return nil
}
// SetSignatures set the signatures of the tx.
func (b *builder) SetSignatures(signatures ...OffchainSignature) error {
n := len(signatures)
signerInfos := make([]*apitx.SignerInfo, n)
rawSigs := make([][]byte, n)
var err error
for i, sig := range signatures {
var mi *apitx.ModeInfo
mi, rawSigs[i], err = b.signatureDataToModeInfoAndSig(sig.Data)
if err != nil {
return err
}
pubKey, err := codectypes.NewAnyWithValue(sig.PubKey)
if err != nil {
return err
}
signerInfos[i] = &apitx.SignerInfo{
PublicKey: &anypb.Any{
TypeUrl: pubKey.TypeUrl,
Value: pubKey.Value,
},
ModeInfo: mi,
Sequence: sig.Sequence,
}
}
b.tx.AuthInfo.SignerInfos = signerInfos
b.tx.Signatures = rawSigs
return nil
}
// signatureDataToModeInfoAndSig converts a SignatureData to a ModeInfo and raw bytes signature.
func (b *builder) signatureDataToModeInfoAndSig(data SignatureData) (*apitx.ModeInfo, []byte, error) {
if data == nil {
return nil, nil, errors.New("empty SignatureData")
}
switch data := data.(type) {
case *SingleSignatureData:
return &apitx.ModeInfo{
Sum: &apitx.ModeInfo_Single_{
Single: &apitx.ModeInfo_Single{Mode: data.SignMode},
},
}, data.Signature, nil
default:
return nil, nil, fmt.Errorf("unexpected signature data type %T", data)
}
}
// modeInfoAndSigToSignatureData converts a ModeInfo and raw bytes signature to a SignatureData.
func modeInfoAndSigToSignatureData(modeInfo *apitx.ModeInfo, sig []byte) (SignatureData, error) {
switch modeInfoType := modeInfo.Sum.(type) {
case *apitx.ModeInfo_Single_:
return &SingleSignatureData{
SignMode: modeInfoType.Single.Mode,
Signature: sig,
}, nil
default:
return nil, fmt.Errorf("unexpected ModeInfo data type %T", modeInfo)
}
}