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

R4R: Limit total number of signatures per transaction #2800

Merged
merged 10 commits into from
Nov 15, 2018
Merged
Show file tree
Hide file tree
Changes from 8 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
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ BREAKING CHANGES

* SDK
* [\#2752](https://github.com/cosmos/cosmos-sdk/pull/2752) Don't hardcode bondable denom.
* [\#2019](https://github.com/cosmos/cosmos-sdk/issues/2019) Cap total number of signatures. Current per-transaction limit is 7, and if that is exceeded transaction is rejected.

* Tendermint

Expand Down
6 changes: 6 additions & 0 deletions types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const (
CodeOutOfGas CodeType = 12
CodeMemoTooLarge CodeType = 13
CodeInsufficientFee CodeType = 14
CodeTooManySignatures CodeType = 15

// CodespaceRoot is a codespace for error codes in this file only.
// Notice that 0 is an "unset" codespace, which can be overridden with
Expand Down Expand Up @@ -103,6 +104,8 @@ func CodeToDefaultMsg(code CodeType) string {
return "memo too large"
case CodeInsufficientFee:
return "insufficient fee"
case CodeTooManySignatures:
return "maximum numer of signatures exceeded"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

numer -> number

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argh! Noted, will fix in another PR. Thanks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have misspell in CI?

default:
return unknownCodeMsg(code)
}
Expand Down Expand Up @@ -155,6 +158,9 @@ func ErrMemoTooLarge(msg string) Error {
func ErrInsufficientFee(msg string) Error {
return newErrorWithRootCodespace(CodeInsufficientFee, msg)
}
func ErrTooManySignatures(msg string) Error {
return newErrorWithRootCodespace(CodeTooManySignatures, msg)
}

//----------------------------------------
// Error & sdkError
Expand Down
26 changes: 26 additions & 0 deletions x/auth/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"bytes"
"encoding/hex"
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/multisig"
"github.com/tendermint/tendermint/crypto/secp256k1"
)

Expand All @@ -17,6 +19,8 @@ const (
maxMemoCharacters = 100
// how much gas = 1 atom
gasPerUnitCost = 1000
// max total number of sigs per tx
txSigLimit = 7
)

// NewAnteHandler returns an AnteHandler that checks
Expand Down Expand Up @@ -74,6 +78,16 @@ func NewAnteHandler(am AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler {
stdSigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice.
signerAddrs := stdTx.GetSigners()

sigCount := 0
alessio marked this conversation as resolved.
Show resolved Hide resolved
for i := 0; i < len(stdSigs); i++ {
sigCount += countSubKeys(stdSigs[i].PubKey)
if sigCount > txSigLimit {
return newCtx, sdk.ErrTooManySignatures(fmt.Sprintf(
"signatures: %d, limit: %d", sigCount, txSigLimit),
).Result(), true
}
}

// create the list of all sign bytes
signBytesList := getSignBytesList(newCtx.ChainID(), stdTx, stdSigs)
signerAccs, res := getSignerAccs(newCtx, am, signerAddrs)
Expand Down Expand Up @@ -308,3 +322,15 @@ func getSignBytesList(chainID string, stdTx StdTx, stdSigs []StdSignature) (sign
}
return
}

func countSubKeys(pub crypto.PubKey) int {
v, ok := pub.(*multisig.PubKeyMultisigThreshold)
if !ok {
return 1
}
nkeys := 0
for _, subkey := range v.PubKeys {
nkeys += countSubKeys(subkey)
}
return nkeys
}
75 changes: 75 additions & 0 deletions x/auth/ante_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/multisig"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/tendermint/tendermint/libs/log"
)
Expand Down Expand Up @@ -714,3 +715,77 @@ func TestAdjustFeesByGas(t *testing.T) {
})
}
}

func TestCountSubkeys(t *testing.T) {
genPubKeys := func(n int) []crypto.PubKey {
var ret []crypto.PubKey
for i := 0; i < n; i++ {
ret = append(ret, secp256k1.GenPrivKey().PubKey())
}
return ret
}
genMultiKey := func(n, k int, keysGen func(n int) []crypto.PubKey) crypto.PubKey {
return multisig.NewPubKeyMultisigThreshold(k, keysGen(n))
}
type args struct {
pub crypto.PubKey
}
mkey := genMultiKey(5, 4, genPubKeys)
mkeyType := mkey.(*multisig.PubKeyMultisigThreshold)
mkeyType.PubKeys = append(mkeyType.PubKeys, genMultiKey(6, 5, genPubKeys))
tests := []struct {
name string
args args
want int
}{
{"single key", args{secp256k1.GenPrivKey().PubKey()}, 1},
{"multi sig key", args{genMultiKey(5, 4, genPubKeys)}, 5},
{"multi multi sig", args{mkey}, 11},
}
for _, tt := range tests {
t.Run(tt.name, func(T *testing.T) {
require.Equal(t, tt.want, countSubKeys(tt.args.pub))
})
}
}

func TestAnteHandlerSigLimitExceeded(t *testing.T) {
// setup
ms, capKey, capKey2 := setupMultiStore()
cdc := codec.New()
RegisterBaseAccount(cdc)
mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount)
feeCollector := NewFeeCollectionKeeper(cdc, capKey2)
anteHandler := NewAnteHandler(mapper, feeCollector)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger())
ctx = ctx.WithBlockHeight(1)

// keys and addresses
priv1, addr1 := privAndAddr()
priv2, addr2 := privAndAddr()
priv3, addr3 := privAndAddr()
priv4, addr4 := privAndAddr()
priv5, addr5 := privAndAddr()
priv6, addr6 := privAndAddr()
priv7, addr7 := privAndAddr()
priv8, addr8 := privAndAddr()

// set the accounts
acc1 := mapper.NewAccountWithAddress(ctx, addr1)
acc1.SetCoins(newCoins())
mapper.SetAccount(ctx, acc1)
acc2 := mapper.NewAccountWithAddress(ctx, addr2)
acc2.SetCoins(newCoins())
mapper.SetAccount(ctx, acc2)

var tx sdk.Tx
msg := newTestMsg(addr1, addr2, addr3, addr4, addr5, addr6, addr7, addr8)
msgs := []sdk.Msg{msg}
fee := newStdFee()

// test rejection logic
privs, accnums, seqs := []crypto.PrivKey{priv1, priv2, priv3, priv4, priv5, priv6, priv7, priv8},
[]int64{0, 0, 0, 0, 0, 0, 0, 0}, []int64{0, 0, 0, 0, 0, 0, 0, 0}
tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee)
checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeTooManySignatures)
}