cosmos-sdk/server/rosetta/client_offline.go

222 lines
6.0 KiB
Go

package rosetta
import (
"context"
"encoding/hex"
"strings"
"github.com/btcsuite/btcd/btcec"
"github.com/coinbase/rosetta-sdk-go/types"
crgerrs "github.com/tendermint/cosmos-rosetta-gateway/errors"
"github.com/tendermint/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
)
func (c *Client) OperationStatuses() []*types.OperationStatus {
return []*types.OperationStatus{
{
Status: StatusSuccess,
Successful: true,
},
{
Status: StatusReverted,
Successful: false,
},
}
}
func (c *Client) Version() string {
return c.version
}
func (c *Client) SupportedOperations() []string {
var supportedOperations []string
for _, ii := range c.ir.ListImplementations("cosmos.base.v1beta1.Msg") {
resolve, err := c.ir.Resolve(ii)
if err != nil {
continue
}
if _, ok := resolve.(Msg); ok {
supportedOperations = append(supportedOperations, strings.TrimLeft(ii, "/"))
}
}
supportedOperations = append(supportedOperations, OperationFee)
return supportedOperations
}
func (c *Client) SignedTx(ctx context.Context, txBytes []byte, signatures []*types.Signature) (signedTxBytes []byte, err error) {
TxConfig := c.getTxConfig()
rawTx, err := TxConfig.TxDecoder()(txBytes)
if err != nil {
return nil, err
}
txBldr, err := TxConfig.WrapTxBuilder(rawTx)
if err != nil {
return nil, err
}
var sigs = make([]signing.SignatureV2, len(signatures))
for i, signature := range signatures {
if signature.PublicKey.CurveType != types.Secp256k1 {
return nil, crgerrs.ErrUnsupportedCurve
}
cmp, err := btcec.ParsePubKey(signature.PublicKey.Bytes, btcec.S256())
if err != nil {
return nil, err
}
compressedPublicKey := make([]byte, secp256k1.PubKeySize)
copy(compressedPublicKey, cmp.SerializeCompressed())
pubKey := &secp256k1.PubKey{Key: compressedPublicKey}
accountInfo, err := c.accountInfo(ctx, sdk.AccAddress(pubKey.Address()).String(), nil)
if err != nil {
return nil, err
}
sig := signing.SignatureV2{
PubKey: pubKey,
Data: &signing.SingleSignatureData{
SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
Signature: signature.Bytes,
},
Sequence: accountInfo.GetSequence(),
}
sigs[i] = sig
}
if err = txBldr.SetSignatures(sigs...); err != nil {
return nil, err
}
txBytes, err = c.getTxConfig().TxEncoder()(txBldr.GetTx())
if err != nil {
return nil, err
}
return txBytes, nil
}
func (c *Client) ConstructionPayload(_ context.Context, request *types.ConstructionPayloadsRequest) (resp *types.ConstructionPayloadsResponse, err error) {
// check if there is at least one operation
if len(request.Operations) < 1 {
return nil, crgerrs.WrapError(crgerrs.ErrInvalidOperation, "expected at least one operation")
}
// convert rosetta operations to sdk msgs and fees (if present)
msgs, fee, err := opsToMsgsAndFees(c.ir, request.Operations)
if err != nil {
return nil, crgerrs.WrapError(crgerrs.ErrInvalidOperation, err.Error())
}
metadata, err := getMetadataFromPayloadReq(request)
if err != nil {
return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, err.Error())
}
txFactory := tx.Factory{}.WithAccountNumber(metadata.AccountNumber).WithChainID(metadata.ChainID).
WithGas(metadata.Gas).WithSequence(metadata.Sequence).WithMemo(metadata.Memo).WithFees(fee.String())
TxConfig := c.getTxConfig()
txFactory = txFactory.WithTxConfig(TxConfig)
txBldr, err := tx.BuildUnsignedTx(txFactory, msgs...)
if err != nil {
return nil, err
}
// Sign_mode_legacy_amino is being used as default here, as sign_mode_direct
// needs the signer infos to be set before hand but rosetta doesn't have a way
// to do this yet. To be revisited in future versions of sdk and rosetta
if txFactory.SignMode() == signing.SignMode_SIGN_MODE_UNSPECIFIED {
txFactory = txFactory.WithSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
}
signerData := authsigning.SignerData{
ChainID: txFactory.ChainID(),
AccountNumber: txFactory.AccountNumber(),
Sequence: txFactory.Sequence(),
}
signBytes, err := TxConfig.SignModeHandler().GetSignBytes(txFactory.SignMode(), signerData, txBldr.GetTx())
if err != nil {
return nil, err
}
txBytes, err := TxConfig.TxEncoder()(txBldr.GetTx())
if err != nil {
return nil, err
}
accIdentifiers := getAccountIdentifiersByMsgs(msgs)
payloads := make([]*types.SigningPayload, len(accIdentifiers))
for i, accID := range accIdentifiers {
payloads[i] = &types.SigningPayload{
AccountIdentifier: accID,
Bytes: crypto.Sha256(signBytes),
SignatureType: types.Ecdsa,
}
}
return &types.ConstructionPayloadsResponse{
UnsignedTransaction: hex.EncodeToString(txBytes),
Payloads: payloads,
}, nil
}
func getAccountIdentifiersByMsgs(msgs []sdk.Msg) []*types.AccountIdentifier {
var accIdentifiers []*types.AccountIdentifier
for _, msg := range msgs {
for _, signer := range msg.GetSigners() {
accIdentifiers = append(accIdentifiers, &types.AccountIdentifier{Address: signer.String()})
}
}
return accIdentifiers
}
func (c *Client) PreprocessOperationsToOptions(_ context.Context, req *types.ConstructionPreprocessRequest) (options map[string]interface{}, err error) {
operations := req.Operations
if len(operations) < 1 {
return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, "invalid number of operations")
}
msgs, err := opsToMsgs(c.ir, operations)
if err != nil {
return nil, crgerrs.WrapError(crgerrs.ErrInvalidOperation, err.Error())
}
if len(msgs) < 1 || len(msgs[0].GetSigners()) < 1 {
return nil, crgerrs.WrapError(crgerrs.ErrInvalidOperation, "operation produced no msg or signers")
}
memo, ok := req.Metadata["memo"]
if !ok {
memo = ""
}
defaultGas := float64(200000)
gas := req.SuggestedFeeMultiplier
if gas == nil {
gas = &defaultGas
}
return map[string]interface{}{
OptionAddress: msgs[0].GetSigners()[0],
OptionMemo: memo,
OptionGas: gas,
}, nil
}