Skip to content

Commit cb86efa

Browse files
ValarDragoncwgoes
authored andcommitted
Merge PR #2376: auth: Don't recalculate mempool fees for every msg signer, misc. cleanup
This PR begins improving the godocs for the auth module, and begins cleaning up the Ante handler. Additionally we previously calculated if the fee was sufficient for the tx on every single signer. This is now refactored to be more efficient, and have a better logical flow. No changelog entry as this is new to this release.
1 parent 91ee6b0 commit cb86efa

File tree

8 files changed

+132
-100
lines changed

8 files changed

+132
-100
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ IMPROVEMENTS
109109
- [baseapp] Allow any alphanumeric character in route
110110
- [tools] Remove `rm -rf vendor/` from `make get_vendor_deps`
111111
- [x/auth] Recover ErrorOutOfGas panic in order to set sdk.Result attributes correctly
112+
- [x/auth] \#2376 No longer runs any signature in a multi-msg, if any account/sequence number is wrong.
113+
- [x/auth] \#2376 No longer charge gas for subtracting fees
112114
- [x/bank] Unit tests are now table-driven
113115
- [tests] Add tests to example apps in docs
114116
- [tests] Fixes ansible scripts to work with AWS too

PENDING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ FEATURES
100100
* [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) allow operations to specify future operations
101101
* [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) Add benchmarking capabilities, with makefile commands "test_sim_gaia_benchmark, test_sim_gaia_profile"
102102
* [simulation] [\#2349](https://github.com/cosmos/cosmos-sdk/issues/2349) Add time-based future scheduled operations to simulator
103+
* [x/auth] \#2376 Remove FeePayer() from StdTx
103104
* [x/stake] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Implement
104105
basis for the validator commission model.
105106

cmd/gaia/app/benchmarks/txsize_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func ExampleTxSendSize() {
2626
Outputs: []bank.Output{bank.NewOutput(addr2, coins)},
2727
}
2828
sig, _ := priv1.Sign(msg1.GetSignBytes())
29-
sigs := []auth.StdSignature{auth.StdSignature{nil, sig, 0, 0}}
29+
sigs := []auth.StdSignature{{nil, sig, 0, 0}}
3030
tx := auth.NewStdTx([]sdk.Msg{msg1}, auth.NewStdFee(0, coins...), sigs, "")
3131
fmt.Println(len(cdc.MustMarshalBinaryBare([]sdk.Msg{msg1})))
3232
fmt.Println(len(cdc.MustMarshalBinaryBare(tx)))

docs/sdk/core/app3.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Now that we have a native model for accounts, it's time to introduce the native
133133

134134
```go
135135
// StdTx is a standard way to wrap a Msg with Fee and Signatures.
136-
// NOTE: the first signature is the FeePayer (Signatures must not be nil).
136+
// NOTE: the first signature is the fee payer (Signatures must not be nil).
137137
type StdTx struct {
138138
Msgs []sdk.Msg `json:"msg"`
139139
Fee StdFee `json:"fee"`
@@ -259,8 +259,7 @@ the same message could be executed over and over again.
259259
The PubKey is required for signature verification, but it is only required in
260260
the StdSignature once. From that point on, it will be stored in the account.
261261

262-
The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`,
263-
as provided by the `FeePayer(tx Tx) sdk.AccAddress` function.
262+
The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`.
264263

265264
## CoinKeeper
266265

x/auth/account.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import (
88
"github.com/tendermint/tendermint/crypto"
99
)
1010

11-
// Account is a standard account using a sequence number for replay protection
12-
// and a pubkey for authentication.
11+
// Account is an interface used to store coins at a given address within state.
12+
// It presumes a notion of sequence numbers for replay protection,
13+
// a notion of account numbers for replay protection for previously pruned accounts,
14+
// and a pubkey for authentication purposes.
15+
//
16+
// Many complex conditions can be used in the concrete struct which implements Account.
1317
type Account interface {
1418
GetAddress() sdk.AccAddress
1519
SetAddress(sdk.AccAddress) error // errors if already set.
@@ -35,9 +39,11 @@ type AccountDecoder func(accountBytes []byte) (Account, error)
3539

3640
var _ Account = (*BaseAccount)(nil)
3741

38-
// BaseAccount - base account structure.
39-
// Extend this by embedding this in your AppAccount.
40-
// See the examples/basecoin/types/account.go for an example.
42+
// BaseAccount - a base account structure.
43+
// This can be extended by embedding within in your AppAccount.
44+
// There are examples of this in: examples/basecoin/types/account.go.
45+
// However one doesn't have to use BaseAccount as long as your struct
46+
// implements Account.
4147
type BaseAccount struct {
4248
Address sdk.AccAddress `json:"address"`
4349
Coins sdk.Coins `json:"coins"`
@@ -118,7 +124,7 @@ func (acc *BaseAccount) SetSequence(seq int64) error {
118124
//----------------------------------------
119125
// Wire
120126

121-
// Most users shouldn't use this, but this comes handy for tests.
127+
// Most users shouldn't use this, but this comes in handy for tests.
122128
func RegisterBaseAccount(cdc *codec.Codec) {
123129
cdc.RegisterInterface((*Account)(nil), nil)
124130
cdc.RegisterConcrete(&BaseAccount{}, "cosmos-sdk/BaseAccount", nil)

x/auth/ante.go

Lines changed: 112 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,18 @@ import (
1212
)
1313

1414
const (
15-
deductFeesCost sdk.Gas = 10
16-
memoCostPerByte sdk.Gas = 1
17-
ed25519VerifyCost = 59
18-
secp256k1VerifyCost = 100
19-
maxMemoCharacters = 100
20-
feeDeductionGasFactor = 0.001
15+
memoCostPerByte sdk.Gas = 1
16+
ed25519VerifyCost = 59
17+
secp256k1VerifyCost = 100
18+
maxMemoCharacters = 100
19+
// how much gas = 1 atom
20+
gasPerUnitCost = 1000
2121
)
2222

2323
// NewAnteHandler returns an AnteHandler that checks
2424
// and increments sequence numbers, checks signatures & account numbers,
2525
// and deducts fees from the first signer.
2626
func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler {
27-
2827
return func(
2928
ctx sdk.Context, tx sdk.Tx, simulate bool,
3029
) (newCtx sdk.Context, res sdk.Result, abort bool) {
@@ -35,13 +34,17 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler {
3534
return ctx, sdk.ErrInternal("tx must be StdTx").Result(), true
3635
}
3736

38-
// set the gas meter
39-
if simulate {
40-
newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
41-
} else {
42-
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas))
37+
// Ensure that the provided fees meet a minimum threshold for the validator, if this is a CheckTx.
38+
// This is only for local mempool purposes, and thus is only ran on check tx.
39+
if ctx.IsCheckTx() && !simulate {
40+
res := ensureSufficientMempoolFees(ctx, stdTx)
41+
if !res.IsOK() {
42+
return newCtx, res, true
43+
}
4344
}
4445

46+
newCtx = setGasMeter(simulate, ctx, stdTx)
47+
4548
// AnteHandlers must have their own defer/recover in order
4649
// for the BaseApp to know how much gas was used!
4750
// This is because the GasMeter is created in the AnteHandler,
@@ -65,64 +68,49 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler {
6568
if err != nil {
6669
return newCtx, err.Result(), true
6770
}
68-
69-
sigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice.
70-
signerAddrs := stdTx.GetSigners()
71-
msgs := tx.GetMsgs()
72-
7371
// charge gas for the memo
7472
newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo")
7573

76-
// Get the sign bytes (requires all account & sequence numbers and the fee)
77-
sequences := make([]int64, len(sigs))
78-
accNums := make([]int64, len(sigs))
79-
for i := 0; i < len(sigs); i++ {
80-
sequences[i] = sigs[i].Sequence
81-
accNums[i] = sigs[i].AccountNumber
82-
}
83-
fee := stdTx.Fee
74+
// stdSigs contains the sequence number, account number, and signatures
75+
stdSigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice.
76+
signerAddrs := stdTx.GetSigners()
8477

85-
// Check sig and nonce and collect signer accounts.
86-
var signerAccs = make([]Account, len(signerAddrs))
87-
for i := 0; i < len(sigs); i++ {
88-
signerAddr, sig := signerAddrs[i], sigs[i]
78+
// create the list of all sign bytes
79+
signBytesList := getSignBytesList(newCtx.ChainID(), stdTx, stdSigs)
80+
signerAccs, res := getSignerAccs(newCtx, am, signerAddrs)
81+
if !res.IsOK() {
82+
return newCtx, res, true
83+
}
84+
res = validateAccNumAndSequence(signerAccs, stdSigs)
85+
if !res.IsOK() {
86+
return newCtx, res, true
87+
}
8988

90-
// check signature, return account with incremented nonce
91-
signBytes := StdSignBytes(newCtx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo())
92-
signerAcc, res := processSig(newCtx, am, signerAddr, sig, signBytes, simulate)
89+
// first sig pays the fees
90+
if !stdTx.Fee.Amount.IsZero() {
91+
// signerAccs[0] is the fee payer
92+
signerAccs[0], res = deductFees(signerAccs[0], stdTx.Fee)
9393
if !res.IsOK() {
9494
return newCtx, res, true
9595
}
96+
fck.addCollectedFees(newCtx, stdTx.Fee.Amount)
97+
}
9698

97-
requiredFees := adjustFeesByGas(ctx.MinimumFees(), fee.Gas)
98-
// fees must be greater than the minimum set by the validator adjusted by gas
99-
if ctx.IsCheckTx() && !simulate && !ctx.MinimumFees().IsZero() && fee.Amount.IsLT(requiredFees) {
100-
// validators reject any tx from the mempool with less than the minimum fee per gas * gas factor
101-
return newCtx, sdk.ErrInsufficientFee(fmt.Sprintf(
102-
"insufficient fee, got: %q required: %q", fee.Amount, requiredFees)).Result(), true
103-
}
104-
105-
// first sig pays the fees
106-
// Can this function be moved outside of the loop?
107-
if i == 0 && !fee.Amount.IsZero() {
108-
newCtx.GasMeter().ConsumeGas(deductFeesCost, "deductFees")
109-
signerAcc, res = deductFees(signerAcc, fee)
110-
if !res.IsOK() {
111-
return newCtx, res, true
112-
}
113-
fck.addCollectedFees(newCtx, fee.Amount)
99+
for i := 0; i < len(stdSigs); i++ {
100+
// check signature, return account with incremented nonce
101+
signerAccs[i], res = processSig(newCtx, signerAccs[i], stdSigs[i], signBytesList[i], simulate)
102+
if !res.IsOK() {
103+
return newCtx, res, true
114104
}
115105

116106
// Save the account.
117-
am.SetAccount(newCtx, signerAcc)
118-
signerAccs[i] = signerAcc
107+
am.SetAccount(newCtx, signerAccs[i])
119108
}
120109

121110
// cache the signer accounts in the context
122111
newCtx = WithSigners(newCtx, signerAccs)
123112

124113
// TODO: tx tags (?)
125-
126114
return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false // continue...
127115
}
128116
}
@@ -150,42 +138,45 @@ func validateBasic(tx StdTx) (err sdk.Error) {
150138
return nil
151139
}
152140

153-
// verify the signature and increment the sequence.
154-
// if the account doesn't have a pubkey, set it.
155-
func processSig(
156-
ctx sdk.Context, am AccountMapper,
157-
addr sdk.AccAddress, sig StdSignature, signBytes []byte, simulate bool) (
158-
acc Account, res sdk.Result) {
159-
// Get the account.
160-
acc = am.GetAccount(ctx, addr)
161-
if acc == nil {
162-
return nil, sdk.ErrUnknownAddress(addr.String()).Result()
141+
func getSignerAccs(ctx sdk.Context, am AccountMapper, addrs []sdk.AccAddress) (accs []Account, res sdk.Result) {
142+
accs = make([]Account, len(addrs))
143+
for i := 0; i < len(accs); i++ {
144+
accs[i] = am.GetAccount(ctx, addrs[i])
145+
if accs[i] == nil {
146+
return nil, sdk.ErrUnknownAddress(addrs[i].String()).Result()
147+
}
163148
}
149+
return
150+
}
164151

165-
accnum := acc.GetAccountNumber()
166-
seq := acc.GetSequence()
152+
func validateAccNumAndSequence(accs []Account, sigs []StdSignature) sdk.Result {
153+
for i := 0; i < len(accs); i++ {
154+
accnum := accs[i].GetAccountNumber()
155+
seq := accs[i].GetSequence()
156+
// Check account number.
157+
if accnum != sigs[i].AccountNumber {
158+
return sdk.ErrInvalidSequence(
159+
fmt.Sprintf("Invalid account number. Got %d, expected %d", sigs[i].AccountNumber, accnum)).Result()
160+
}
167161

168-
// Check account number.
169-
if accnum != sig.AccountNumber {
170-
return nil, sdk.ErrInvalidSequence(
171-
fmt.Sprintf("Invalid account number. Got %d, expected %d", sig.AccountNumber, accnum)).Result()
162+
// Check sequence number.
163+
if seq != sigs[i].Sequence {
164+
return sdk.ErrInvalidSequence(
165+
fmt.Sprintf("Invalid sequence. Got %d, expected %d", sigs[i].Sequence, seq)).Result()
166+
}
172167
}
168+
return sdk.Result{}
169+
}
173170

174-
// Check sequence number.
175-
if seq != sig.Sequence {
176-
return nil, sdk.ErrInvalidSequence(
177-
fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result()
178-
}
179-
err := acc.SetSequence(seq + 1)
180-
if err != nil {
181-
// Handle w/ #870
182-
panic(err)
183-
}
171+
// verify the signature and increment the sequence.
172+
// if the account doesn't have a pubkey, set it.
173+
func processSig(ctx sdk.Context,
174+
acc Account, sig StdSignature, signBytes []byte, simulate bool) (updatedAcc Account, res sdk.Result) {
184175
pubKey, res := processPubKey(acc, sig, simulate)
185176
if !res.IsOK() {
186177
return nil, res
187178
}
188-
err = acc.SetPubKey(pubKey)
179+
err := acc.SetPubKey(pubKey)
189180
if err != nil {
190181
return nil, sdk.ErrInternal("setting PubKey on signer's account").Result()
191182
}
@@ -195,7 +186,14 @@ func processSig(
195186
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
196187
}
197188

198-
return
189+
// increment the sequence number
190+
err = acc.SetSequence(acc.GetSequence() + 1)
191+
if err != nil {
192+
// Handle w/ #870
193+
panic(err)
194+
}
195+
196+
return acc, res
199197
}
200198

201199
var dummySecp256k1Pubkey secp256k1.PubKeySecp256k1
@@ -244,8 +242,9 @@ func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) {
244242
}
245243

246244
func adjustFeesByGas(fees sdk.Coins, gas int64) sdk.Coins {
247-
gasCost := int64(float64(gas) * feeDeductionGasFactor)
245+
gasCost := gas / gasPerUnitCost
248246
gasFees := make(sdk.Coins, len(fees))
247+
// TODO: Make this not price all coins in the same way
249248
for i := 0; i < len(fees); i++ {
250249
gasFees[i] = sdk.NewInt64Coin(fees[i].Denom, gasCost)
251250
}
@@ -272,5 +271,37 @@ func deductFees(acc Account, fee StdFee) (Account, sdk.Result) {
272271
return acc, sdk.Result{}
273272
}
274273

274+
func ensureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result {
275+
// currently we use a very primitive gas pricing model with a constant gasPrice.
276+
// adjustFeesByGas handles calculating the amount of fees required based on the provided gas.
277+
// TODO: Make the gasPrice not a constant, and account for tx size.
278+
requiredFees := adjustFeesByGas(ctx.MinimumFees(), stdTx.Fee.Gas)
279+
280+
if !ctx.MinimumFees().IsZero() && stdTx.Fee.Amount.IsLT(requiredFees) {
281+
// validators reject any tx from the mempool with less than the minimum fee per gas * gas factor
282+
return sdk.ErrInsufficientFee(fmt.Sprintf(
283+
"insufficient fee, got: %q required: %q", stdTx.Fee.Amount, requiredFees)).Result()
284+
}
285+
return sdk.Result{}
286+
}
287+
288+
func setGasMeter(simulate bool, ctx sdk.Context, stdTx StdTx) sdk.Context {
289+
// set the gas meter
290+
if simulate {
291+
return ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
292+
}
293+
return ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas))
294+
}
295+
296+
func getSignBytesList(chainID string, stdTx StdTx, stdSigs []StdSignature) (signatureBytesList [][]byte) {
297+
signatureBytesList = make([][]byte, len(stdSigs))
298+
for i := 0; i < len(stdSigs); i++ {
299+
signatureBytesList[i] = StdSignBytes(chainID,
300+
stdSigs[i].AccountNumber, stdSigs[i].Sequence,
301+
stdTx.Fee, stdTx.Msgs, stdTx.Memo)
302+
}
303+
return
304+
}
305+
275306
// BurnFeeHandler burns all fees (decreasing total supply)
276307
func BurnFeeHandler(_ sdk.Context, _ sdk.Tx, _ sdk.Coins) {}

x/auth/stdtx.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
var _ sdk.Tx = (*StdTx)(nil)
1212

1313
// StdTx is a standard way to wrap a Msg with Fee and Signatures.
14-
// NOTE: the first signature is the FeePayer (Signatures must not be nil).
14+
// NOTE: the first signature is the fee payer (Signatures must not be nil).
1515
type StdTx struct {
1616
Msgs []sdk.Msg `json:"msg"`
1717
Fee StdFee `json:"fee"`
@@ -63,13 +63,6 @@ func (tx StdTx) GetMemo() string { return tx.Memo }
6363
// .Empty().
6464
func (tx StdTx) GetSignatures() []StdSignature { return tx.Signatures }
6565

66-
// FeePayer returns the address responsible for paying the fees
67-
// for the transactions. It's the first address returned by msg.GetSigners().
68-
// If GetSigners() is empty, this panics.
69-
func FeePayer(tx sdk.Tx) sdk.AccAddress {
70-
return tx.GetMsgs()[0].GetSigners()[0]
71-
}
72-
7366
//__________________________________________________________
7467

7568
// StdFee includes the amount of coins paid in fees and the maximum

x/auth/stdtx_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func TestStdTx(t *testing.T) {
2323
require.Equal(t, msgs, tx.GetMsgs())
2424
require.Equal(t, sigs, tx.GetSignatures())
2525

26-
feePayer := FeePayer(tx)
26+
feePayer := tx.GetSigners()[0]
2727
require.Equal(t, addr, feePayer)
2828
}
2929

0 commit comments

Comments
 (0)