Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rosetta] implement balance tracking and redo tx construction #8729

Merged
merged 39 commits into from
Mar 11, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
702ea89
change(rosetta): data api block and tx parsing
fdymylja Mar 1, 2021
8f711a1
change(rosetta): finalize data API
fdymylja Mar 1, 2021
ab91ce3
add: converter
fdymylja Mar 3, 2021
b3bf68b
fix: casting error message
fdymylja Mar 3, 2021
593f26d
change: rework construction API to support every possible transaction…
fdymylja Mar 4, 2021
fbdeabc
change: make construction stateless
fdymylja Mar 4, 2021
232d83f
chore: cleanup api
fdymylja Mar 4, 2021
b43553e
chore: cleanup api
fdymylja Mar 4, 2021
e2b1a01
chore: reorder methods declaration
fdymylja Mar 5, 2021
5292379
add: signed tx tests
fdymylja Mar 5, 2021
976f2e2
add: ops and signers test
fdymylja Mar 5, 2021
a489652
fix: begin and endblock tx conversions
fdymylja Mar 5, 2021
18bd9e3
add: begin and endblock invalid tests
fdymylja Mar 5, 2021
a32c005
add: balance and signing components tests
fdymylja Mar 5, 2021
9ad9d28
change: remove staking tests
fdymylja Mar 5, 2021
52be187
Merge branch 'master' into frojdi/rosetta-balance-tracking
fdymylja Mar 5, 2021
7375263
chore: lint
fdymylja Mar 5, 2021
2246c6c
chore: lint
fdymylja Mar 5, 2021
4377a09
revert: makefile rosetta test
fdymylja Mar 5, 2021
a1f82bc
chore: lint again
fdymylja Mar 5, 2021
89de69c
chore: move tests to package based ones
fdymylja Mar 5, 2021
687aef2
chore: lint
fdymylja Mar 5, 2021
8a9a140
chore: lint
fdymylja Mar 5, 2021
bf428f4
chore: cleanup ci
fdymylja Mar 5, 2021
c7da349
Merge branch 'master' into frojdi/rosetta-balance-tracking
Mar 5, 2021
8af9112
Merge branch 'master' into frojdi/rosetta-balance-tracking
Mar 5, 2021
b3f5410
chore: address documentation changes
fdymylja Mar 5, 2021
0bb7b16
chore: address documentation changes
fdymylja Mar 5, 2021
daeb5f0
Merge branch 'master' into frojdi/rosetta-balance-tracking
fdymylja Mar 5, 2021
fe10978
Merge branch 'master' into frojdi/rosetta-balance-tracking
Mar 10, 2021
57e684a
Update docs server/rosetta/client_online.go
fdymylja Mar 11, 2021
0c3bad6
Update docs server/rosetta/client_online.go
fdymylja Mar 11, 2021
d6e1622
cleanup spacing server/rosetta/converter_test.go
fdymylja Mar 11, 2021
f28f18d
revert: baseapp.md
fdymylja Mar 11, 2021
2f7a5c8
add: docs for interface implementation
fdymylja Mar 11, 2021
9fda8a2
remove: converter_test.go utils anonymous type
fdymylja Mar 11, 2021
958fba5
change: set interface name constant
fdymylja Mar 11, 2021
6354cfa
chore: add CHANGELOG.md entry
fdymylja Mar 11, 2021
3e47bb4
Merge branch 'master' into frojdi/rosetta-balance-tracking
Mar 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: cleanup api
  • Loading branch information
fdymylja committed Mar 4, 2021
commit 232d83feffdac7cb0ae0c64f98200206dd8382dd
75 changes: 5 additions & 70 deletions server/rosetta/client_offline.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,14 @@ package rosetta
import (
"context"
"encoding/hex"
"encoding/json"
"log"
"strings"

"github.com/tendermint/tendermint/crypto"

authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"

banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"

"github.com/coinbase/rosetta-sdk-go/types"
crgerrs "github.com/tendermint/cosmos-rosetta-gateway/errors"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
)

func (c *Client) OperationStatuses() []*types.OperationStatus {
Expand Down Expand Up @@ -63,8 +56,6 @@ func (c *Client) SignedTx(_ context.Context, txBytes []byte, signatures []*types
}

func (c *Client) ConstructionPayload(_ context.Context, request *types.ConstructionPayloadsRequest) (resp *types.ConstructionPayloadsResponse, err error) {
b, _ := json.Marshal(request)
log.Printf("raw req: %s", b)
// check if there is at least one operation
if len(request.Operations) < 1 {
return nil, crgerrs.WrapError(crgerrs.ErrInvalidOperation, "expected at least one operation")
Expand All @@ -77,72 +68,16 @@ func (c *Client) ConstructionPayload(_ context.Context, request *types.Construct

metadata := new(ConstructionMetadata)
if err = metadata.FromMetadata(request.Metadata); err != nil {
return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, err.Error())
}
feeAmt, err := sdk.ParseCoinsNormalized(metadata.GasPrice)
if err != nil {
return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, err.Error())
}

//
builder := c.txConfig.NewTxBuilder()
_ = builder.SetMsgs(tx.GetMsgs()...)
builder.SetFeeAmount(feeAmt)
builder.SetGasLimit(metadata.GasLimit)
builder.SetMemo(metadata.Memo)

tx = builder.GetTx()

accIdentifiers := tx.GetSigners()

payloads := make([]*types.SigningPayload, len(accIdentifiers))
signersData := make([]signing.SignatureV2, len(accIdentifiers))
for i, accID := range accIdentifiers {
// we expect pubkeys to be ordered... TODO(fdymylja): maybe make ordering not matter?
signerData := authsigning.SignerData{
ChainID: metadata.ChainID,
AccountNumber: metadata.SignersData[i].AccountNumber,
Sequence: metadata.SignersData[i].Sequence,
}
// 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
signBytes, err := c.txConfig.SignModeHandler().GetSignBytes(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, signerData, tx)
if err != nil {
return nil, crgerrs.WrapError(crgerrs.ErrBadArgument, "signing error: "+err.Error())
}

payloads[i] = &types.SigningPayload{
AccountIdentifier: &types.AccountIdentifier{Address: accID.String()},
Bytes: crypto.Sha256(signBytes),
SignatureType: types.Ecdsa,
}

pk, err := c.converter.ToSDK().PubKey(request.PublicKeys[i])
if err != nil {
return nil, err
}

signersData[i] = signing.SignatureV2{
PubKey: pk,
Data: &signing.SingleSignatureData{},
Sequence: metadata.SignersData[i].Sequence,
}
return nil, err
}

// we set the signature data so we carry information regarding public key
// then afterwards we just need to set the signed bytes
err = builder.SetSignatures(signersData...)
if err != nil {
return nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error())
}
// encode tx
encodedTx, err := c.txEncode(builder.GetTx())
txBytes, payloads, err := c.converter.ToRosetta().SigningComponents(tx, metadata, request.PublicKeys)
if err != nil {
return nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error())
return nil, err
}

return &types.ConstructionPayloadsResponse{
UnsignedTransaction: hex.EncodeToString(encodedTx),
UnsignedTransaction: hex.EncodeToString(txBytes),
Payloads: payloads,
}, nil
}
Expand Down
2 changes: 0 additions & 2 deletions server/rosetta/client_online.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ type Client struct {

tmRPC tmrpc.Client

txEncode sdk.TxEncoder
txConfig client.TxConfig
version string

Expand All @@ -74,7 +73,6 @@ func NewClient(cfg *Config) (*Client, error) {
bank: nil,
ir: cfg.InterfaceRegistry,
tmRPC: nil,
txEncode: txConfig.TxEncoder(),
txConfig: txConfig,
version: fmt.Sprintf("%s/%s", info.AppName, v),
converter: NewConverter(cfg.Codec, cfg.InterfaceRegistry, txConfig),
Expand Down
109 changes: 107 additions & 2 deletions server/rosetta/converter.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package rosetta

import (
"bytes"
"encoding/json"
"fmt"
"reflect"

"github.com/tendermint/tendermint/crypto"

"github.com/btcsuite/btcd/btcec"
crgtypes "github.com/tendermint/cosmos-rosetta-gateway/types"
tmcoretypes "github.com/tendermint/tendermint/rpc/core/types"
Expand Down Expand Up @@ -62,6 +65,8 @@ type ToRosettaConverter interface {
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)
// 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
Expand Down Expand Up @@ -97,6 +102,7 @@ type converter struct {
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
}
Expand All @@ -111,8 +117,16 @@ func NewConverter(cdc *codec.ProtoCodec, ir codectypes.InterfaceRegistry, cfg sd
},
txDecode: cfg.TxDecoder(),
txEncode: cfg.TxEncoder(),
ir: ir,
cdc: cdc,
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,
}
}

Expand Down Expand Up @@ -675,3 +689,94 @@ func (c converter) PubKey(pubKey *rosettatypes.PublicKey) (cryptotypes.PubKey, e

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{
ChainID: metadata.ChainID,
AccountNumber: metadata.SignersData[i].AccountNumber,
Sequence: metadata.SignersData[i].Sequence,
}

// 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
// encode tx
txBytes, err = c.txEncode(builder.GetTx())
if err != nil {
return nil, nil, crgerrs.WrapError(crgerrs.ErrCodec, err.Error())
}

return
}