222 lines
6.0 KiB
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
|
||
|
}
|