Skip to content

Commit

Permalink
Add support for protobuf TxGenerator and SIGN_MODE_DIRECT (cosmos#6385)
Browse files Browse the repository at this point in the history
* Add TxWrapper, encoder, decoder and DirectModeHandler

* fix pkg name

* Update API and leave test TODO's

* Update TxWrapper API

* tests for tx wrapper (cosmos#6410)

* WIP: added test for direct mode handler

* updated code

* Add msg

* Update TxWrapper API

* Fix pubkey declaration

* Add pubkey for tests

* Fix SetFee

* Remove logs

* Avoid global var declaration for tests

* Add test for GetPubKeys

* Fix direct signing tests

* Add more test cases for GetSignBytes

* Revert SetFee API

* Remove logs

* Refactor tests

Co-authored-by: anilCSE <[email protected]>
Co-authored-by: sahith-narahari <[email protected]>

* Refactoring

* Refactoring

* Integrate SignatureV2 API

* Fix wrapper tests

* Fix tests

* Linting and API tweaks

* Update API

* WIP on updating API

* Fix tests

* Update to new SigVerifiableTx

* Rename

* Update docs to reflect ADR 020

* proto-gen

* proto docs

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* Add tests

* Refactor and improving test coverage

* WIP on test coverage

* WIP on test coverage

* proto-gen

* Fix CompactBitArray.Size() bug

* Rename

* Remove Builder interface

* Address review comments

* Update x/auth/tx/sigs.go

Co-authored-by: Federico Kunze <[email protected]>

* Update x/auth/tx/encoder.go

Co-authored-by: Federico Kunze <[email protected]>

* Update x/auth/tx/encoder.go

Co-authored-by: Federico Kunze <[email protected]>

* Address review feedback

* Fix build issues

* Resolve conflicts

* Fix ValidateBasic test coverage

* Add test for malicious multisig

Co-authored-by: atheeshp <[email protected]>
Co-authored-by: anilCSE <[email protected]>
Co-authored-by: sahith-narahari <[email protected]>
Co-authored-by: Alexander Bezobchuk <[email protected]>
Co-authored-by: Federico Kunze <[email protected]>
  • Loading branch information
6 people authored Jul 6, 2020
1 parent feb6977 commit 2f44fbf
Show file tree
Hide file tree
Showing 56 changed files with 2,542 additions and 400 deletions.
3 changes: 2 additions & 1 deletion client/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import (
gocontext "context"
"fmt"

"github.com/cosmos/cosmos-sdk/codec/types"
gogogrpc "github.com/gogo/protobuf/grpc"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding"
"google.golang.org/grpc/encoding/proto"

"github.com/cosmos/cosmos-sdk/codec/types"
)

var _ gogogrpc.ClientConn = Context{}
Expand Down
260 changes: 219 additions & 41 deletions client/testutil/suite.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package testutil

import (
"bytes"

"github.com/cosmos/cosmos-sdk/codec/testdata"
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/secp256k1"

"github.com/cosmos/cosmos-sdk/client"

Expand All @@ -25,86 +30,259 @@ func NewTxGeneratorTestSuite(txGenerator client.TxGenerator) *TxGeneratorTestSui
}

func (s *TxGeneratorTestSuite) TestTxBuilderGetTx() {
stdTxBuilder := s.TxGenerator.NewTxBuilder()
tx := stdTxBuilder.GetTx()
txBuilder := s.TxGenerator.NewTxBuilder()
tx := txBuilder.GetTx()
s.Require().NotNil(tx)
s.Require().Equal(len(tx.GetMsgs()), 0)
}

func (s *TxGeneratorTestSuite) TestTxBuilderSetFeeAmount() {
stdTxBuilder := s.TxGenerator.NewTxBuilder()
txBuilder := s.TxGenerator.NewTxBuilder()
feeAmount := sdk.Coins{
sdk.NewInt64Coin("atom", 20000000),
}
stdTxBuilder.SetFeeAmount(feeAmount)
feeTx := stdTxBuilder.GetTx().(sdk.FeeTx)
txBuilder.SetFeeAmount(feeAmount)
feeTx := txBuilder.GetTx()
s.Require().Equal(feeAmount, feeTx.GetFee())
}

func (s *TxGeneratorTestSuite) TestTxBuilderSetGasLimit() {
const newGas uint64 = 300000
stdTxBuilder := s.TxGenerator.NewTxBuilder()
stdTxBuilder.SetGasLimit(newGas)
feeTx := stdTxBuilder.GetTx().(sdk.FeeTx)
txBuilder := s.TxGenerator.NewTxBuilder()
txBuilder.SetGasLimit(newGas)
feeTx := txBuilder.GetTx()
s.Require().Equal(newGas, feeTx.GetGas())
}

func (s *TxGeneratorTestSuite) TestTxBuilderSetMemo() {
const newMemo string = "newfoomemo"
stdTxBuilder := s.TxGenerator.NewTxBuilder()
stdTxBuilder.SetMemo(newMemo)
txWithMemo := stdTxBuilder.GetTx().(sdk.TxWithMemo)
txBuilder := s.TxGenerator.NewTxBuilder()
txBuilder.SetMemo(newMemo)
txWithMemo := txBuilder.GetTx()
s.Require().Equal(txWithMemo.GetMemo(), newMemo)
}

func (s *TxGeneratorTestSuite) TestTxBuilderSetMsgs() {
stdTxBuilder := s.TxGenerator.NewTxBuilder()
tx := stdTxBuilder.GetTx()
err := stdTxBuilder.SetMsgs(sdk.NewTestMsg(), sdk.NewTestMsg())
s.Require().NoError(err)
s.Require().NotEqual(tx, stdTxBuilder.GetTx())
s.Require().Equal(len(stdTxBuilder.GetTx().GetMsgs()), 2)
}
_, _, addr1 := authtypes.KeyTestPubAddr()
_, _, addr2 := authtypes.KeyTestPubAddr()
msg1 := testdata.NewTestMsg(addr1)
msg2 := testdata.NewTestMsg(addr2)
msgs := []sdk.Msg{msg1, msg2}

type HasSignaturesTx interface {
GetSignatures() [][]byte
GetSigners() []sdk.AccAddress
GetPubKeys() []crypto.PubKey // If signer already has pubkey in context, this list will have nil in its place
txBuilder := s.TxGenerator.NewTxBuilder()

err := txBuilder.SetMsgs(msgs...)
s.Require().NoError(err)
tx := txBuilder.GetTx()
s.Require().Equal(msgs, tx.GetMsgs())
s.Require().Equal([]sdk.AccAddress{addr1, addr2}, tx.GetSigners())
s.Require().Equal(addr1, tx.FeePayer())
s.Require().Error(tx.ValidateBasic()) // should fail because of no signatures
}

func (s *TxGeneratorTestSuite) TestTxBuilderSetSignatures() {
priv := secp256k1.GenPrivKey()
privKey, pubkey, addr := authtypes.KeyTestPubAddr()
privKey2, pubkey2, _ := authtypes.KeyTestPubAddr()
multisigPk := multisig.NewPubKeyMultisigThreshold(2, []crypto.PubKey{pubkey, pubkey2})

stdTxBuilder := s.TxGenerator.NewTxBuilder()
tx := stdTxBuilder.GetTx()
singleSignatureData := signingtypes.SingleSignatureData{
Signature: priv.PubKey().Bytes(),
SignMode: signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
txBuilder := s.TxGenerator.NewTxBuilder()

// set test msg
msg := testdata.NewTestMsg(addr)
msigAddr := sdk.AccAddress(multisigPk.Address())
msg2 := testdata.NewTestMsg(msigAddr)
err := txBuilder.SetMsgs(msg, msg2)
s.Require().NoError(err)

// check that validation fails
s.Require().Error(txBuilder.GetTx().ValidateBasic())

signModeHandler := s.TxGenerator.SignModeHandler()
s.Require().Contains(signModeHandler.Modes(), signModeHandler.DefaultMode())

// set SignatureV2 without actual signature bytes
sigData1 := &signingtypes.SingleSignatureData{SignMode: signModeHandler.DefaultMode()}
sig1 := signingtypes.SignatureV2{PubKey: pubkey, Data: sigData1}

msigData := multisig.NewMultisig(2)
multisig.AddSignature(msigData, &signingtypes.SingleSignatureData{SignMode: signModeHandler.DefaultMode()}, 0)
multisig.AddSignature(msigData, &signingtypes.SingleSignatureData{SignMode: signModeHandler.DefaultMode()}, 1)
msig := signingtypes.SignatureV2{PubKey: multisigPk, Data: msigData}

// fail validation without required signers
err = txBuilder.SetSignatures(sig1)
s.Require().NoError(err)
sigTx := txBuilder.GetTx()
s.Require().Error(sigTx.ValidateBasic())

err = txBuilder.SetSignatures(sig1, msig)
s.Require().NoError(err)
sigTx = txBuilder.GetTx()
s.Require().Len(sigTx.GetSignatures(), 2)
sigsV2, err := sigTx.GetSignaturesV2()
s.Require().NoError(err)
s.Require().Len(sigsV2, 2)
s.Require().True(sigEquals(sig1, sigsV2[0]))
s.Require().True(sigEquals(msig, sigsV2[1]))
s.Require().Equal([]sdk.AccAddress{addr, msigAddr}, sigTx.GetSigners())
s.Require().NoError(sigTx.ValidateBasic())

// sign transaction
signerData := signing.SignerData{
ChainID: "test",
AccountNumber: 1,
AccountSequence: 2,
}
signBytes, err := signModeHandler.GetSignBytes(signModeHandler.DefaultMode(), signerData, sigTx)
s.Require().NoError(err)
sigBz, err := privKey.Sign(signBytes)
s.Require().NoError(err)

err := stdTxBuilder.SetSignatures(signingtypes.SignatureV2{
PubKey: priv.PubKey(),
Data: &singleSignatureData,
})
sigTx := stdTxBuilder.GetTx().(HasSignaturesTx)
signerData = signing.SignerData{
ChainID: "test",
AccountNumber: 3,
AccountSequence: 4,
}
mSignBytes, err := signModeHandler.GetSignBytes(signModeHandler.DefaultMode(), signerData, sigTx)
s.Require().NoError(err)
mSigBz1, err := privKey.Sign(mSignBytes)
s.Require().NoError(err)
s.Require().NotEqual(tx, stdTxBuilder.GetTx())
s.Require().Equal(sigTx.GetSignatures()[0], priv.PubKey().Bytes())
mSigBz2, err := privKey2.Sign(mSignBytes)
s.Require().NoError(err)
msigData = multisig.NewMultisig(2)
multisig.AddSignature(msigData, &signingtypes.SingleSignatureData{
SignMode: signModeHandler.DefaultMode(), Signature: mSigBz1}, 0)
multisig.AddSignature(msigData, &signingtypes.SingleSignatureData{
SignMode: signModeHandler.DefaultMode(), Signature: mSigBz2}, 0)

// set signature
sigData1.Signature = sigBz
sig1 = signingtypes.SignatureV2{PubKey: pubkey, Data: sigData1}
msig = signingtypes.SignatureV2{PubKey: multisigPk, Data: msigData}
err = txBuilder.SetSignatures(sig1, msig)
s.Require().NoError(err)
sigTx = txBuilder.GetTx()
s.Require().Len(sigTx.GetSignatures(), 2)
sigsV2, err = sigTx.GetSignaturesV2()
s.Require().NoError(err)
s.Require().Len(sigsV2, 2)
s.Require().True(sigEquals(sig1, sigsV2[0]))
s.Require().True(sigEquals(msig, sigsV2[1]))
s.Require().Equal([]sdk.AccAddress{addr, msigAddr}, sigTx.GetSigners())
s.Require().NoError(sigTx.ValidateBasic())
}

func sigEquals(sig1, sig2 signingtypes.SignatureV2) bool {
if !sig1.PubKey.Equals(sig2.PubKey) {
return false
}

if sig1.Data == nil && sig2.Data == nil {
return true
}

return sigDataEquals(sig1.Data, sig2.Data)
}

func sigDataEquals(data1, data2 signingtypes.SignatureData) bool {
switch data1 := data1.(type) {
case *signingtypes.SingleSignatureData:
data2, ok := data2.(*signingtypes.SingleSignatureData)
if !ok {
return false
}

if data1.SignMode != data2.SignMode {
return false
}

return bytes.Equal(data1.Signature, data2.Signature)
case *signingtypes.MultiSignatureData:
data2, ok := data2.(*signingtypes.MultiSignatureData)
if !ok {
return false
}

if data1.BitArray.ExtraBitsStored != data2.BitArray.ExtraBitsStored {
return false
}

if !bytes.Equal(data1.BitArray.Elems, data2.BitArray.Elems) {
return false
}

if len(data1.Signatures) != len(data2.Signatures) {
return false
}

for i, s := range data1.Signatures {
if !sigDataEquals(s, data2.Signatures[i]) {
return false
}
}

return true
default:
return false
}
}

func (s *TxGeneratorTestSuite) TestTxEncodeDecode() {
_, pubkey, addr := authtypes.KeyTestPubAddr()
feeAmount := sdk.Coins{sdk.NewInt64Coin("atom", 150)}
gasLimit := uint64(50000)
memo := "foomemo"
msg := testdata.NewTestMsg(addr)
dummySig := []byte("dummySig")
sig := signingtypes.SignatureV2{
PubKey: pubkey,
Data: &signingtypes.SingleSignatureData{
SignMode: signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
Signature: dummySig,
},
}

txBuilder := s.TxGenerator.NewTxBuilder()
txBuilder.SetFeeAmount(sdk.Coins{sdk.NewInt64Coin("atom", 150)})
txBuilder.SetGasLimit(50000)
txBuilder.SetMemo("foomemo")
txBuilder.SetFeeAmount(feeAmount)
txBuilder.SetGasLimit(gasLimit)
txBuilder.SetMemo(memo)
err := txBuilder.SetMsgs(msg)
s.Require().NoError(err)
err = txBuilder.SetSignatures(sig)
s.Require().NoError(err)
tx := txBuilder.GetTx()

// Encode transaction
s.T().Log("encode transaction")
txBytes, err := s.TxGenerator.TxEncoder()(tx)
s.Require().NoError(err)
s.Require().NotNil(txBytes)

s.T().Log("decode transaction")
tx2, err := s.TxGenerator.TxDecoder()(txBytes)
s.Require().NoError(err)
s.Require().Equal(tx, tx2)
tx3, ok := tx2.(signing.SigFeeMemoTx)
s.Require().True(ok)
s.Require().Equal([]sdk.Msg{msg}, tx3.GetMsgs())
s.Require().Equal(feeAmount, tx3.GetFee())
s.Require().Equal(gasLimit, tx3.GetGas())
s.Require().Equal(memo, tx3.GetMemo())
s.Require().Equal([][]byte{dummySig}, tx3.GetSignatures())
s.Require().Equal([]crypto.PubKey{pubkey}, tx3.GetPubKeys())

s.T().Log("JSON encode transaction")
jsonTxBytes, err := s.TxGenerator.TxJSONEncoder()(tx)
s.Require().NoError(err)
s.Require().NotNil(jsonTxBytes)

s.T().Log("JSON decode transaction")
tx2, err = s.TxGenerator.TxJSONDecoder()(jsonTxBytes)
s.Require().NoError(err)
tx3, ok = tx2.(signing.SigFeeMemoTx)
s.Require().True(ok)
s.Require().Equal([]sdk.Msg{msg}, tx3.GetMsgs())
s.Require().Equal(feeAmount, tx3.GetFee())
s.Require().Equal(gasLimit, tx3.GetGas())
s.Require().Equal(memo, tx3.GetMemo())
s.Require().Equal([][]byte{dummySig}, tx3.GetSignatures())
s.Require().Equal([]crypto.PubKey{pubkey}, tx3.GetPubKeys())
}
6 changes: 3 additions & 3 deletions client/tx/tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import (
"errors"
"testing"

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

"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/tests"

"github.com/cosmos/cosmos-sdk/x/auth/ante"

"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/client"
Expand Down Expand Up @@ -111,7 +111,7 @@ func TestBuildUnsignedTx(t *testing.T) {
tx, err := tx.BuildUnsignedTx(txf, msg)
require.NoError(t, err)
require.NotNil(t, tx)
require.Empty(t, tx.GetTx().(ante.SigVerifiableTx).GetSignatures())
require.Empty(t, tx.GetTx().(signing.SigVerifiableTx).GetSignatures())
}

func TestSign(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion client/tx_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type (
// signatures, and provide canonical bytes to sign over. The transaction must
// also know how to encode itself.
TxBuilder interface {
GetTx() sdk.Tx
GetTx() signing.SigFeeMemoTx

SetMsgs(msgs ...sdk.Msg) error
SetSignatures(signatures ...signingtypes.SignatureV2) error
Expand Down
Loading

0 comments on commit 2f44fbf

Please sign in to comment.