Co-authored-by: Marko <marko@baricevic.me> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
319 lines
7.8 KiB
Go
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)
|
|
}
|
|
}
|