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

Fix sequence number handling for LegacyAmino > SignatureV2 #7285

Merged
merged 14 commits into from
Sep 16, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
14 changes: 0 additions & 14 deletions types/tx/signing/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,6 @@ type SignatureV2 struct {
// Sequence is the sequence of this account. Only populated in
// SIGN_MODE_DIRECT.
Sequence uint64

// Ugly flag to keep backwards-compatibility with Amino StdSignatures.
// In SIGN_MODE_DIRECT, sequence is in AuthInfo, and will thus be populated
// in the Sequence field above. The ante handler then checks this Sequence
// with the actual sequence on-chain.
// In SIGN_MODE_LEGACY_AMINO_JSON, sequence is signed via StdSignDoc, and
// checked during signature verification. It's not populated in the
// Sequence field above. This flag indicates that the Sequence field should
// be skipped in ante handlers.
// TLDR;
// - false (by default) in SIGN_MODE_DIRECT
// - true in SIGN_MODE_LEGACY_AMINO_JSON
// ref: https://github.com/cosmos/cosmos-sdk/issues/7229
SkipSequenceCheck bool
clevinson marked this conversation as resolved.
Show resolved Hide resolved
}

// SignatureDataToProto converts a SignatureData to SignatureDescriptor_Data.
Expand Down
30 changes: 27 additions & 3 deletions x/auth/ante/sigverify.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,27 @@ func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler authsigning.S
}
}

// OnlyLegacyAminoSigners checks SignatureData to see if all
// signers are using SIGN_MODE_LEGACY_AMINO_JSON. If this is the case
// then the corresponding SignatureV2 struct will not have account sequence
// explicitly set, and we should skip the explicit verification of sig.Sequence
// in the SigVerificationDecorator's AnteHanlde function.
clevinson marked this conversation as resolved.
Show resolved Hide resolved
func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool {
clevinson marked this conversation as resolved.
Show resolved Hide resolved
switch v := sigData.(type) {
case *signing.SingleSignatureData:
return v.SignMode == signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
case *signing.MultiSignatureData:
for _, s := range v.Signatures {
if !OnlyLegacyAminoSigners(s) {
return false
}
}
return true
default:
return false
}
}

func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
// no need to verify signatures on recheck tx
if ctx.IsReCheckTx() {
Expand Down Expand Up @@ -210,8 +231,9 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
// When using Amino StdSignatures, we actually don't have the Sequence in
// the SignatureV2 struct (it's only in the SignDoc). In this case, we
// cannot check sequence directly, and must do it via signature
// verification.
if !sig.SkipSequenceCheck {
// verification (in the VerifySignature call below).
onlyAminoSigners := OnlyLegacyAminoSigners(sig.Data)
if !onlyAminoSigners {
if sig.Sequence != acc.GetSequence() {
return ctx, sdkerrors.Wrapf(
sdkerrors.ErrWrongSequence,
Expand All @@ -237,7 +259,9 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, tx)
if err != nil {
var errMsg string
if sig.SkipSequenceCheck {
if onlyAminoSigners {
// If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number,
// and therefore communicate sequence number as a potential cause of error.
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID)
} else {
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID)
Expand Down
88 changes: 88 additions & 0 deletions x/auth/client/rest/rest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ package rest_test

import (
"fmt"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -105,6 +109,90 @@ func (s *IntegrationTestSuite) TestBroadcastTxRequest() {
s.Require().NotEmpty(txRes.TxHash)
}

func (s *IntegrationTestSuite) TestMultipleSyncBroadcastTxRequests() {

val0 := s.network.Validators[0]
txConfig := authtypes.StdTxConfig{Cdc: s.cfg.LegacyAmino}

// First test transaction from validator should have sequence=1 (non-genesis tx)
testCases := []struct {
desc string
sequence uint64
shouldErr bool
}{
{
"First tx (correct sequence)",
1,
false,
},
{
"Second tx (correct sequence)",
2,
false,
},
{
"Third tx (incorrect sequence)",
9,
true,
},
}
for _, tc := range testCases {
s.Run(fmt.Sprintf("Case %s", tc.desc), func() {

msg := &types.MsgSend{
val0.Address,
val0.Address,
sdk.Coins{sdk.NewInt64Coin("foo", 100)},
}

// prepare txBuilder with msg
txBuilder := txConfig.NewTxBuilder()
feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}
gasLimit := testdata.NewTestGasLimit()
txBuilder.SetMsgs(msg)
txBuilder.SetFeeAmount(feeAmount)
txBuilder.SetGasLimit(gasLimit)

// setup txFactory
txFactory := tx.Factory{}
txFactory = txFactory.
WithChainID(val0.ClientCtx.ChainID).
WithKeybase(val0.ClientCtx.Keyring).
WithTxConfig(txConfig).
WithSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON).
WithSequence(tc.sequence)

// sign Tx (offline mode so we can manually set sequence number)
err := authclient.SignTx(txFactory, val0.ClientCtx, val0.Moniker, txBuilder, true)
s.Require().NoError(err)

// broadcast test with sync mode, as we want to run CheckTx to verify account sequence is correct
stdTx := txBuilder.GetTx().(authtypes.StdTx)
res, err := s.broadcastReq(stdTx, "sync")
s.Require().NoError(err)

var txRes sdk.TxResponse
// NOTE: this uses amino explicitly, don't migrate it!
s.Require().NoError(s.cfg.LegacyAmino.UnmarshalJSON(res, &txRes))
// we check for a exitCode=0, indicating a successful broadcast
if tc.shouldErr {
var sigVerifyFailureCode uint32 = 4
s.Require().Equal(sigVerifyFailureCode, txRes.Code,
"Testcase '%s': Expected signature verification failure {Code: %d} from TxResponse. "+
"Found {Code: %d, RawLog: '%v'}",
tc.desc, sigVerifyFailureCode, txRes.Code, txRes.RawLog,
)
} else {
s.Require().Equal(uint32(0), txRes.Code,
"Testcase '%s': TxResponse errored unexpectedly. Err: {Code: %d, RawLog: '%v'}",
tc.desc, txRes.Code, txRes.RawLog,
)
}
})
}

}

func (s *IntegrationTestSuite) broadcastReq(stdTx authtypes.StdTx, mode string) ([]byte, error) {
val := s.network.Validators[0]

Expand Down
1 change: 0 additions & 1 deletion x/auth/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ func (s *TxConfigTestSuite) TestTxEncodeDecode() {
SignMode: signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
Signature: dummySig,
},
SkipSequenceCheck: s.TxConfig.SignModeHandler().DefaultMode() == signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
}

txBuilder := s.TxConfig.NewTxBuilder()
Expand Down
5 changes: 2 additions & 3 deletions x/auth/types/stdtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,8 @@ func StdSignatureToSignatureV2(cdc *codec.LegacyAmino, sig StdSignature) (signin
}

return signing.SignatureV2{
PubKey: pk,
Data: data,
SkipSequenceCheck: true,
PubKey: pk,
Data: data,
}, nil
}

Expand Down