package rosetta import ( "bytes" "encoding/json" "fmt" "reflect" "github.com/btcsuite/btcd/btcec" rosettatypes "github.com/coinbase/rosetta-sdk-go/types" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" tmcoretypes "github.com/tendermint/tendermint/rpc/coretypes" tmtypes "github.com/tendermint/tendermint/types" sdkclient "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/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" crgerrs "github.com/cosmos/cosmos-sdk/server/rosetta/lib/errors" crgtypes "github.com/cosmos/cosmos-sdk/server/rosetta/lib/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/tx/signing" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" auth "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) // Converter is a utility that can be used to convert // back and forth from rosetta to sdk and tendermint types // IMPORTANT NOTES: // - IT SHOULD BE USED ONLY TO DEAL WITH THINGS // IN A STATELESS WAY! IT SHOULD NEVER INTERACT DIRECTLY // WITH TENDERMINT RPC AND COSMOS GRPC // // - IT SHOULD RETURN cosmos rosetta gateway error types! type Converter interface { // ToSDK exposes the methods that convert // rosetta types to cosmos sdk and tendermint types ToSDK() ToSDKConverter // ToRosetta exposes the methods that convert // sdk and tendermint types to rosetta types ToRosetta() ToRosettaConverter } // ToRosettaConverter is an interface that exposes // all the functions used to convert sdk and // tendermint types to rosetta known types type ToRosettaConverter interface { // BlockResponse returns a block response given a result block BlockResponse(block *tmcoretypes.ResultBlock) crgtypes.BlockResponse // BeginBlockToTx converts the given begin block hash to rosetta transaction hash BeginBlockTxHash(blockHash []byte) string // EndBlockTxHash converts the given endblock hash to rosetta transaction hash EndBlockTxHash(blockHash []byte) string // Amounts converts sdk.Coins to rosetta.Amounts Amounts(ownedCoins []sdk.Coin, availableCoins sdk.Coins) []*rosettatypes.Amount // Ops converts an sdk.Msg to rosetta operations Ops(status string, msg sdk.Msg) ([]*rosettatypes.Operation, error) // OpsAndSigners takes raw transaction bytes and returns rosetta operations and the expected signers OpsAndSigners(txBytes []byte) (ops []*rosettatypes.Operation, signers []*rosettatypes.AccountIdentifier, err error) // Meta converts an sdk.Msg to rosetta metadata Meta(msg sdk.Msg) (meta map[string]interface{}, err error) // SignerData returns account signing data from a queried any account SignerData(anyAccount *codectypes.Any) (*SignerData, error) // SigningComponents returns rosetta's components required to build a signable transaction SigningComponents(tx authsigning.Tx, metadata *ConstructionMetadata, rosPubKeys []*rosettatypes.PublicKey) (txBytes []byte, payloadsToSign []*rosettatypes.SigningPayload, err error) // Tx converts a tendermint transaction and tx result if provided to a rosetta tx Tx(rawTx tmtypes.Tx, txResult *abci.ResponseDeliverTx) (*rosettatypes.Transaction, error) // TxIdentifiers converts a tendermint tx to transaction identifiers TxIdentifiers(txs []tmtypes.Tx) []*rosettatypes.TransactionIdentifier // BalanceOps converts events to balance operations BalanceOps(status string, events []abci.Event) []*rosettatypes.Operation // SyncStatus converts a tendermint status to sync status SyncStatus(status *tmcoretypes.ResultStatus) *rosettatypes.SyncStatus // Peers converts tendermint peers to rosetta Peers(peers []tmcoretypes.Peer) []*rosettatypes.Peer } // ToSDKConverter is an interface that exposes // all the functions used to convert rosetta types // to tendermint and sdk types type ToSDKConverter interface { // UnsignedTx converts rosetta operations to an unsigned cosmos sdk transactions UnsignedTx(ops []*rosettatypes.Operation) (tx authsigning.Tx, err error) // SignedTx adds the provided signatures after decoding the unsigned transaction raw bytes // and returns the signed tx bytes SignedTx(txBytes []byte, signatures []*rosettatypes.Signature) (signedTxBytes []byte, err error) // Msg converts metadata to an sdk message Msg(meta map[string]interface{}, msg sdk.Msg) (err error) // HashToTxType returns the transaction type (end block, begin block or deliver tx) // and the real hash to query in order to get information HashToTxType(hashBytes []byte) (txType TransactionType, realHash []byte) // PubKey attempts to convert a rosetta public key to cosmos sdk one PubKey(pk *rosettatypes.PublicKey) (cryptotypes.PubKey, error) } type converter struct { newTxBuilder func() sdkclient.TxBuilder txBuilderFromTx func(tx sdk.Tx) (sdkclient.TxBuilder, error) txDecode sdk.TxDecoder txEncode sdk.TxEncoder bytesToSign func(tx authsigning.Tx, signerData authsigning.SignerData) (b []byte, err error) ir codectypes.InterfaceRegistry cdc *codec.ProtoCodec } func NewConverter(cdc *codec.ProtoCodec, ir codectypes.InterfaceRegistry, cfg sdkclient.TxConfig) Converter { return converter{ newTxBuilder: cfg.NewTxBuilder, txBuilderFromTx: cfg.WrapTxBuilder, txDecode: cfg.TxDecoder(), txEncode: cfg.TxEncoder(), bytesToSign: func(tx authsigning.Tx, signerData authsigning.SignerData) (b []byte, err error) { bytesToSign, err := cfg.SignModeHandler().GetSignBytes(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, signerData, tx) if err != nil { return nil, err } return crypto.Sha256(bytesToSign), nil }, ir: ir, cdc: cdc, } } func (c converter) ToSDK() ToSDKConverter { return c } func (c converter) ToRosetta() ToRosettaConverter { return c } // OpsToUnsignedTx returns all the sdk.Msgs given the operations func (c converter) UnsignedTx(ops []*rosettatypes.Operation) (tx authsigning.Tx, err error) { builder := c.newTxBuilder() var msgs []sdk.Msg for i := 0; i < len(ops); i++ { op := ops[i] protoMessage, err := c.ir.Resolve(op.Type) if err != nil { return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, "operation not found: "+op.Type) } msg, ok := protoMessage.(sdk.Msg) if !ok { return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, "operation is not a valid supported sdk.Msg: "+op.Type) } err = c.Msg(op.Metadata, msg) if err != nil { return nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } // verify message correctness if err = msg.ValidateBasic(); err != nil { return nil, crgerrs.WrapError( crgerrs.ErrBadArgument, fmt.Sprintf("validation of operation at index %d failed: %s", op.OperationIdentifier.Index, err), ) } signers := msg.GetSigners() // check if there are enough signers if len(signers) == 0 { return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, fmt.Sprintf("operation at index %d got no signers", op.OperationIdentifier.Index)) } // append the msg msgs = append(msgs, msg) // if there's only one signer then simply continue if len(signers) == 1 { continue } // after we have got the msg, we need to verify if the message has multiple signers // if it has got multiple signers, then we need to fetch all the related operations // which involve the other signers of the msg, we expect to find them in order // so if the msg is named "v1.test.Send" and it expects 3 signers, the next 3 operations // must be with the same name "v1.test.Send" and contain the other signers // then we can just skip their processing for j := 0; j < len(signers)-1; j++ { skipOp := ops[i+j] // get the next index // verify that the operation is equal to the new one if skipOp.Type != op.Type { return nil, crgerrs.WrapError( crgerrs.ErrBadArgument, fmt.Sprintf("operation at index %d should have had type %s got: %s", i+j, op.Type, skipOp.Type), ) } if !reflect.DeepEqual(op.Metadata, skipOp.Metadata) { return nil, crgerrs.WrapError( crgerrs.ErrBadArgument, fmt.Sprintf("operation at index %d should have had metadata equal to %#v, got: %#v", i+j, op.Metadata, skipOp.Metadata)) } i++ // increase so we skip it } } if err := builder.SetMsgs(msgs...); err != nil { return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, err.Error()) } return builder.GetTx(), nil } // Msg unmarshals the rosetta metadata to the given sdk.Msg func (c converter) Msg(meta map[string]interface{}, msg sdk.Msg) error { metaBytes, err := json.Marshal(meta) if err != nil { return err } return c.cdc.UnmarshalJSON(metaBytes, msg) } func (c converter) Meta(msg sdk.Msg) (meta map[string]interface{}, err error) { b, err := c.cdc.MarshalJSON(msg) if err != nil { return nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } err = json.Unmarshal(b, &meta) if err != nil { return nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } return } // Ops will create an operation for each msg signer // with the message proto name as type, and the raw fields // as metadata func (c converter) Ops(status string, msg sdk.Msg) ([]*rosettatypes.Operation, error) { opName := sdk.MsgTypeURL(msg) meta, err := c.Meta(msg) if err != nil { return nil, err } ops := make([]*rosettatypes.Operation, len(msg.GetSigners())) for i, signer := range msg.GetSigners() { op := &rosettatypes.Operation{ Type: opName, Status: &status, Account: &rosettatypes.AccountIdentifier{Address: signer.String()}, Metadata: meta, } ops[i] = op } return ops, nil } // Tx converts a tendermint raw transaction and its result (if provided) to a rosetta transaction func (c converter) Tx(rawTx tmtypes.Tx, txResult *abci.ResponseDeliverTx) (*rosettatypes.Transaction, error) { // decode tx tx, err := c.txDecode(rawTx) if err != nil { return nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } // get initial status, as per sdk design, if one msg fails // the whole TX will be considered failing, so we can't have // 1 msg being success and 1 msg being reverted status := StatusTxSuccess switch txResult { // if nil, we're probably checking an unconfirmed tx // or trying to build a new transaction, so status // is not put inside case nil: status = "" // set the status default: if txResult.Code != abci.CodeTypeOK { status = StatusTxReverted } } // get operations from msgs msgs := tx.GetMsgs() var rawTxOps []*rosettatypes.Operation for _, msg := range msgs { ops, err := c.Ops(status, msg) if err != nil { return nil, err } rawTxOps = append(rawTxOps, ops...) } // now get balance events from response deliver tx var balanceOps []*rosettatypes.Operation // tx result might be nil, in case we're querying an unconfirmed tx from the mempool if txResult != nil { balanceOps = c.BalanceOps(status, txResult.Events) } // now normalize indexes totalOps := AddOperationIndexes(rawTxOps, balanceOps) return &rosettatypes.Transaction{ TransactionIdentifier: &rosettatypes.TransactionIdentifier{Hash: fmt.Sprintf("%X", rawTx.Hash())}, Operations: totalOps, }, nil } func (c converter) BalanceOps(status string, events []abci.Event) []*rosettatypes.Operation { var ops []*rosettatypes.Operation for _, e := range events { balanceOps, ok := sdkEventToBalanceOperations(status, e) if !ok { continue } ops = append(ops, balanceOps...) } return ops } // sdkEventToBalanceOperations converts an event to a rosetta balance operation // it will panic if the event is malformed because it might mean the sdk spec // has changed and rosetta needs to reflect those changes too. // The balance operations are multiple, one for each denom. func sdkEventToBalanceOperations(status string, event abci.Event) (operations []*rosettatypes.Operation, isBalanceEvent bool) { var ( accountIdentifier string coinChange sdk.Coins isSub bool ) switch event.Type { default: return nil, false case banktypes.EventTypeCoinSpent: spender, err := sdk.AccAddressFromBech32(event.Attributes[0].Value) if err != nil { panic(err) } coins, err := sdk.ParseCoinsNormalized(event.Attributes[1].Value) if err != nil { panic(err) } isSub = true coinChange = coins accountIdentifier = spender.String() case banktypes.EventTypeCoinReceived: receiver, err := sdk.AccAddressFromBech32(event.Attributes[0].Value) if err != nil { panic(err) } coins, err := sdk.ParseCoinsNormalized(event.Attributes[1].Value) if err != nil { panic(err) } isSub = false coinChange = coins accountIdentifier = receiver.String() // rosetta does not have the concept of burning coins, so we need to mock // the burn as a send to an address that cannot be resolved to anything case banktypes.EventTypeCoinBurn: coins, err := sdk.ParseCoinsNormalized(event.Attributes[1].Value) if err != nil { panic(err) } coinChange = coins accountIdentifier = BurnerAddressIdentifier } operations = make([]*rosettatypes.Operation, len(coinChange)) for i, coin := range coinChange { value := coin.Amount.String() // in case the event is a subtract balance one the rewrite value with // the negative coin identifier if isSub { value = "-" + value } op := &rosettatypes.Operation{ Type: event.Type, Status: &status, Account: &rosettatypes.AccountIdentifier{Address: accountIdentifier}, Amount: &rosettatypes.Amount{ Value: value, Currency: &rosettatypes.Currency{ Symbol: coin.Denom, Decimals: 0, }, }, } operations[i] = op } return operations, true } // Amounts converts []sdk.Coin to rosetta amounts func (c converter) Amounts(ownedCoins []sdk.Coin, availableCoins sdk.Coins) []*rosettatypes.Amount { amounts := make([]*rosettatypes.Amount, len(availableCoins)) ownedCoinsMap := make(map[string]sdk.Int, len(availableCoins)) for _, ownedCoin := range ownedCoins { ownedCoinsMap[ownedCoin.Denom] = ownedCoin.Amount } for i, coin := range availableCoins { value, owned := ownedCoinsMap[coin.Denom] if !owned { amounts[i] = &rosettatypes.Amount{ Value: sdk.NewInt(0).String(), Currency: &rosettatypes.Currency{ Symbol: coin.Denom, }, } continue } amounts[i] = &rosettatypes.Amount{ Value: value.String(), Currency: &rosettatypes.Currency{ Symbol: coin.Denom, }, } } return amounts } // AddOperationIndexes adds the indexes to operations adhering to specific rules: // operations related to messages will be always before than the balance ones func AddOperationIndexes(msgOps []*rosettatypes.Operation, balanceOps []*rosettatypes.Operation) (finalOps []*rosettatypes.Operation) { lenMsgOps := len(msgOps) lenBalanceOps := len(balanceOps) finalOps = make([]*rosettatypes.Operation, 0, lenMsgOps+lenBalanceOps) var currentIndex int64 // add indexes to msg ops for _, op := range msgOps { op.OperationIdentifier = &rosettatypes.OperationIdentifier{ Index: currentIndex, } finalOps = append(finalOps, op) currentIndex++ } // add indexes to balance ops for _, op := range balanceOps { op.OperationIdentifier = &rosettatypes.OperationIdentifier{ Index: currentIndex, } finalOps = append(finalOps, op) currentIndex++ } return finalOps } // EndBlockTxHash produces a mock endblock hash that rosetta can query // for endblock operations, it also serves the purpose of representing // part of the state changes happening at endblock level (balance ones) func (c converter) EndBlockTxHash(hash []byte) string { final := append([]byte{EndBlockHashStart}, hash...) return fmt.Sprintf("%X", final) } // BeginBlockTxHash produces a mock beginblock hash that rosetta can query // for beginblock operations, it also serves the purpose of representing // part of the state changes happening at beginblock level (balance ones) func (c converter) BeginBlockTxHash(hash []byte) string { final := append([]byte{BeginBlockHashStart}, hash...) return fmt.Sprintf("%X", final) } // HashToTxType takes the provided hash bytes from rosetta and discerns if they are // a deliver tx type or endblock/begin block hash, returning the real hash afterwards func (c converter) HashToTxType(hashBytes []byte) (txType TransactionType, realHash []byte) { switch len(hashBytes) { case DeliverTxSize: return DeliverTxTx, hashBytes case BeginEndBlockTxSize: switch hashBytes[0] { case BeginBlockHashStart: return BeginBlockTx, hashBytes[1:] case EndBlockHashStart: return EndBlockTx, hashBytes[1:] default: return UnrecognizedTx, nil } default: return UnrecognizedTx, nil } } // StatusToSyncStatus converts a tendermint status to rosetta sync status func (c converter) SyncStatus(status *tmcoretypes.ResultStatus) *rosettatypes.SyncStatus { // determine sync status var stage = StatusPeerSynced if status.SyncInfo.CatchingUp { stage = StatusPeerSyncing } return &rosettatypes.SyncStatus{ CurrentIndex: &status.SyncInfo.LatestBlockHeight, TargetIndex: nil, // sync info does not allow us to get target height Stage: &stage, } } // TxIdentifiers converts a tendermint raw transactions into an array of rosetta tx identifiers func (c converter) TxIdentifiers(txs []tmtypes.Tx) []*rosettatypes.TransactionIdentifier { converted := make([]*rosettatypes.TransactionIdentifier, len(txs)) for i, tx := range txs { converted[i] = &rosettatypes.TransactionIdentifier{Hash: fmt.Sprintf("%X", tx.Hash())} } return converted } // tmResultBlockToRosettaBlockResponse converts a tendermint result block to block response func (c converter) BlockResponse(block *tmcoretypes.ResultBlock) crgtypes.BlockResponse { var parentBlock *rosettatypes.BlockIdentifier switch block.Block.Height { case 1: parentBlock = &rosettatypes.BlockIdentifier{ Index: 1, Hash: fmt.Sprintf("%X", block.BlockID.Hash.Bytes()), } default: parentBlock = &rosettatypes.BlockIdentifier{ Index: block.Block.Height - 1, Hash: fmt.Sprintf("%X", block.Block.LastBlockID.Hash.Bytes()), } } return crgtypes.BlockResponse{ Block: &rosettatypes.BlockIdentifier{ Index: block.Block.Height, Hash: block.Block.Hash().String(), }, ParentBlock: parentBlock, MillisecondTimestamp: timeToMilliseconds(block.Block.Time), TxCount: int64(len(block.Block.Txs)), } } // Peers converts tm peers to rosetta peers func (c converter) Peers(peers []tmcoretypes.Peer) []*rosettatypes.Peer { converted := make([]*rosettatypes.Peer, len(peers)) for i, peer := range peers { converted[i] = &rosettatypes.Peer{ PeerID: string(peer.ID), Metadata: map[string]interface{}{ "addr": peer.URL, }, } } return converted } // OpsAndSigners takes transactions bytes and returns the operation, is signed is true it will return // the account identifiers which have signed the transaction func (c converter) OpsAndSigners(txBytes []byte) (ops []*rosettatypes.Operation, signers []*rosettatypes.AccountIdentifier, err error) { rosTx, err := c.ToRosetta().Tx(txBytes, nil) if err != nil { return nil, nil, err } ops = rosTx.Operations // get the signers sdkTx, err := c.txDecode(txBytes) if err != nil { return nil, nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } txBuilder, err := c.txBuilderFromTx(sdkTx) if err != nil { return nil, nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } for _, signer := range txBuilder.GetTx().GetSigners() { signers = append(signers, &rosettatypes.AccountIdentifier{ Address: signer.String(), }) } return } func (c converter) SignedTx(txBytes []byte, signatures []*rosettatypes.Signature) (signedTxBytes []byte, err error) { rawTx, err := c.txDecode(txBytes) if err != nil { return nil, err } txBuilder, err := c.txBuilderFromTx(rawTx) if err != nil { return nil, err } notSignedSigs, err := txBuilder.GetTx().GetSignaturesV2() // if err != nil { return nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } if len(notSignedSigs) != len(signatures) { return nil, crgerrs.WrapError( crgerrs.ErrInvalidTransaction, fmt.Sprintf("expected transaction to have signers data matching the provided signatures: %d <-> %d", len(notSignedSigs), len(signatures))) } signedSigs := make([]signing.SignatureV2, len(notSignedSigs)) for i, signature := range signatures { // TODO(fdymylja): here we should check that the public key matches... signedSigs[i] = signing.SignatureV2{ PubKey: notSignedSigs[i].PubKey, Data: &signing.SingleSignatureData{ SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, Signature: signature.Bytes, }, Sequence: notSignedSigs[i].Sequence, } } if err = txBuilder.SetSignatures(signedSigs...); err != nil { return nil, err } txBytes, err = c.txEncode(txBuilder.GetTx()) if err != nil { return nil, err } return txBytes, nil } func (c converter) PubKey(pubKey *rosettatypes.PublicKey) (cryptotypes.PubKey, error) { if pubKey.CurveType != "secp256k1" { return nil, crgerrs.WrapError(crgerrs.ErrUnsupportedCurve, "only secp256k1 supported") } cmp, err := btcec.ParsePubKey(pubKey.Bytes, btcec.S256()) if err != nil { return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, err.Error()) } compressedPublicKey := make([]byte, secp256k1.PubKeySize) copy(compressedPublicKey, cmp.SerializeCompressed()) pk := &secp256k1.PubKey{Key: compressedPublicKey} return pk, nil } // SigningComponents takes a sdk tx and construction metadata and returns signable components func (c converter) SigningComponents(tx authsigning.Tx, metadata *ConstructionMetadata, rosPubKeys []*rosettatypes.PublicKey) (txBytes []byte, payloadsToSign []*rosettatypes.SigningPayload, err error) { // verify metadata correctness feeAmount, err := sdk.ParseCoinsNormalized(metadata.GasPrice) if err != nil { return nil, nil, crgerrs.WrapError(crgerrs.ErrBadArgument, err.Error()) } signers := tx.GetSigners() // assert the signers data provided in options are the same as the expected signing accounts // and that the number of rosetta provided public keys equals the one of the signers if len(metadata.SignersData) != len(signers) || len(signers) != len(rosPubKeys) { return nil, nil, crgerrs.WrapError(crgerrs.ErrBadArgument, "signers data and account identifiers mismatch") } // add transaction metadata builder, err := c.txBuilderFromTx(tx) if err != nil { return nil, nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } builder.SetFeeAmount(feeAmount) builder.SetGasLimit(metadata.GasLimit) builder.SetMemo(metadata.Memo) // build signatures partialSignatures := make([]signing.SignatureV2, len(signers)) payloadsToSign = make([]*rosettatypes.SigningPayload, len(signers)) // pub key ordering matters, in a future release this check might be relaxed for i, signer := range signers { // assert that the provided public keys are correctly ordered // by checking if the signer at index i matches the pubkey at index pubKey, err := c.ToSDK().PubKey(rosPubKeys[0]) if err != nil { return nil, nil, err } if !bytes.Equal(pubKey.Address().Bytes(), signer.Bytes()) { return nil, nil, crgerrs.WrapError( crgerrs.ErrBadArgument, fmt.Sprintf("public key at index %d does not match the expected transaction signer: %X <-> %X", i, rosPubKeys[i].Bytes, signer.Bytes()), ) } // set the signer data signerData := authsigning.SignerData{ Address: signer.String(), ChainID: metadata.ChainID, AccountNumber: metadata.SignersData[i].AccountNumber, Sequence: metadata.SignersData[i].Sequence, PubKey: pubKey, } // get signature bytes signBytes, err := c.bytesToSign(tx, signerData) if err != nil { return nil, nil, crgerrs.WrapError(crgerrs.ErrUnknown, fmt.Sprintf("unable to sign tx: %s", err.Error())) } // set payload payloadsToSign[i] = &rosettatypes.SigningPayload{ AccountIdentifier: &rosettatypes.AccountIdentifier{Address: signer.String()}, Bytes: signBytes, SignatureType: rosettatypes.Ecdsa, } // set partial signature partialSignatures[i] = signing.SignatureV2{ PubKey: pubKey, Data: &signing.SingleSignatureData{}, // needs to be set to empty otherwise the codec will cry Sequence: metadata.SignersData[i].Sequence, } } // now we set the partial signatures in the tx // because we will need to decode the sequence // information of each account in a stateless way err = builder.SetSignatures(partialSignatures...) if err != nil { return nil, nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } // finally encode the tx txBytes, err = c.txEncode(builder.GetTx()) if err != nil { return nil, nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } return txBytes, payloadsToSign, nil } // SignerData converts the given any account to signer data func (c converter) SignerData(anyAccount *codectypes.Any) (*SignerData, error) { var acc auth.AccountI err := c.ir.UnpackAny(anyAccount, &acc) if err != nil { return nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error()) } return &SignerData{ AccountNumber: acc.GetAccountNumber(), Sequence: acc.GetSequence(), }, nil }