-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
auth: Don't recalculate mempool fees for every msg signer, misc. cleanup #2376
Changes from 9 commits
68ee2eb
8097c52
8f817af
9332e4a
199cfe7
a23d454
b513a95
fb657a4
1c59807
543cb7f
344aab8
2d10de3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,20 +12,17 @@ import ( | |
) | ||
|
||
const ( | ||
deductFeesCost sdk.Gas = 10 | ||
memoCostPerByte sdk.Gas = 1 | ||
ed25519VerifyCost = 59 | ||
secp256k1VerifyCost = 100 | ||
maxMemoCharacters = 100 | ||
feeDeductionGasFactor = 0.001 | ||
memoCostPerByte sdk.Gas = 1 | ||
ed25519VerifyCost = 59 | ||
secp256k1VerifyCost = 100 | ||
maxMemoCharacters = 100 | ||
gasPrice = 0.001 | ||
) | ||
|
||
// NewAnteHandler returns an AnteHandler that checks | ||
// and increments sequence numbers, checks signatures & account numbers, | ||
// and deducts fees from the first signer. | ||
// nolint: gocyclo | ||
func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { | ||
|
||
return func( | ||
ctx sdk.Context, tx sdk.Tx, simulate bool, | ||
) (newCtx sdk.Context, res sdk.Result, abort bool) { | ||
|
@@ -36,13 +33,17 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { | |
return ctx, sdk.ErrInternal("tx must be StdTx").Result(), true | ||
} | ||
|
||
// set the gas meter | ||
if simulate { | ||
newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) | ||
} else { | ||
newCtx = ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) | ||
// Ensure that the provided fees meet a minimum threshold for the validator, if this is a CheckTx. | ||
// This is for mempool purposes, and thus is only ran on check tx. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for local/subject mempool purposes, |
||
if ctx.IsCheckTx() && !simulate { | ||
res := ensureSufficientMempoolFees(ctx, stdTx) | ||
if !res.IsOK() { | ||
return newCtx, res, true | ||
} | ||
} | ||
|
||
newCtx = setGasMeter(simulate, ctx, stdTx) | ||
|
||
// AnteHandlers must have their own defer/recover in order | ||
// for the BaseApp to know how much gas was used! | ||
// This is because the GasMeter is created in the AnteHandler, | ||
|
@@ -66,64 +67,49 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { | |
if err != nil { | ||
return newCtx, err.Result(), true | ||
} | ||
|
||
sigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. simply renamed to stdSigs to distinguish from it being the actual cryptographic signature as was previously implied. |
||
signerAddrs := stdTx.GetSigners() | ||
msgs := tx.GetMsgs() | ||
|
||
// charge gas for the memo | ||
newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo") | ||
|
||
// Get the sign bytes (requires all account & sequence numbers and the fee) | ||
sequences := make([]int64, len(sigs)) | ||
accNums := make([]int64, len(sigs)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The need for these was removed by getting the accounts directly with getSignerAccounts, and getSignBytesList |
||
for i := 0; i < len(sigs); i++ { | ||
sequences[i] = sigs[i].Sequence | ||
accNums[i] = sigs[i].AccountNumber | ||
} | ||
fee := stdTx.Fee | ||
// stdSigs contains the sequence number, account number, and signatures | ||
stdSigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice. | ||
signerAddrs := stdTx.GetSigners() | ||
|
||
// Check sig and nonce and collect signer accounts. | ||
var signerAccs = make([]Account, len(signerAddrs)) | ||
for i := 0; i < len(sigs); i++ { | ||
signerAddr, sig := signerAddrs[i], sigs[i] | ||
// create the list of all sign bytes | ||
signBytesList := getSignBytesList(newCtx.ChainID(), stdTx, stdSigs) | ||
signerAccs, res := getSignerAccs(newCtx, am, signerAddrs) | ||
if !res.IsOK() { | ||
return newCtx, res, true | ||
} | ||
res = validateAccNumAndSequence(signerAccs, stdSigs) | ||
if !res.IsOK() { | ||
return newCtx, res, true | ||
} | ||
|
||
// check signature, return account with incremented nonce | ||
signBytes := StdSignBytes(newCtx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo()) | ||
signerAcc, res := processSig(newCtx, am, signerAddr, sig, signBytes, simulate) | ||
// first sig pays the fees | ||
if !stdTx.Fee.Amount.IsZero() { | ||
// signerAccs[0] is the fee payer | ||
signerAccs[0], res = deductFees(signerAccs[0], stdTx.Fee) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see it?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh its on StdTx. I don't think we should use that though, since we already have access to the signerAccs, and doesn't make sense to run that method to get it again. I'd be happy to just make I think we should just remove the feepayer method entirely, as its currently unused. If we want we can refactor this fee deduction as used here into its own method (I think that makes sense as well) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In line of principle, it's not a bad idea to provide SDK clients with a convenience There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah just remove it as it's not part of the interface (and use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you remove it, please mention it as a breaking change 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually using a feepayer variable looks weirder, since we need to update the entry in the array. I've removed the fee payer method though, and added an additional comment for clarity. |
||
if !res.IsOK() { | ||
return newCtx, res, true | ||
} | ||
fck.addCollectedFees(newCtx, stdTx.Fee.Amount) | ||
} | ||
|
||
requiredFees := adjustFeesByGas(ctx.MinimumFees(), fee.Gas) | ||
// fees must be greater than the minimum set by the validator adjusted by gas | ||
if ctx.IsCheckTx() && !simulate && !ctx.MinimumFees().IsZero() && fee.Amount.IsLT(requiredFees) { | ||
// validators reject any tx from the mempool with less than the minimum fee per gas * gas factor | ||
return newCtx, sdk.ErrInsufficientFee(fmt.Sprintf( | ||
"insufficient fee, got: %q required: %q", fee.Amount, requiredFees)).Result(), true | ||
} | ||
|
||
// first sig pays the fees | ||
// Can this function be moved outside of the loop? | ||
if i == 0 && !fee.Amount.IsZero() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code was moved outside of the for loop (right above it!) |
||
newCtx.GasMeter().ConsumeGas(deductFeesCost, "deductFees") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed charging gas for deducting fees, because that's silly lol |
||
signerAcc, res = deductFees(signerAcc, fee) | ||
if !res.IsOK() { | ||
return newCtx, res, true | ||
} | ||
fck.addCollectedFees(newCtx, fee.Amount) | ||
for i := 0; i < len(stdSigs); i++ { | ||
// check signature, return account with incremented nonce | ||
signerAccs[i], res = processSig(newCtx, signerAccs[i], stdSigs[i], signBytesList[i], simulate) | ||
if !res.IsOK() { | ||
return newCtx, res, true | ||
} | ||
|
||
// Save the account. | ||
am.SetAccount(newCtx, signerAcc) | ||
signerAccs[i] = signerAcc | ||
am.SetAccount(newCtx, signerAccs[i]) | ||
} | ||
|
||
// cache the signer accounts in the context | ||
newCtx = WithSigners(newCtx, signerAccs) | ||
|
||
// TODO: tx tags (?) | ||
|
||
return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false // continue... | ||
} | ||
} | ||
|
@@ -151,42 +137,45 @@ func validateBasic(tx StdTx) (err sdk.Error) { | |
return nil | ||
} | ||
|
||
// verify the signature and increment the sequence. | ||
// if the account doesn't have a pubkey, set it. | ||
func processSig( | ||
ctx sdk.Context, am AccountMapper, | ||
addr sdk.AccAddress, sig StdSignature, signBytes []byte, simulate bool) ( | ||
acc Account, res sdk.Result) { | ||
// Get the account. | ||
acc = am.GetAccount(ctx, addr) | ||
if acc == nil { | ||
return nil, sdk.ErrUnknownAddress(addr.String()).Result() | ||
func getSignerAccs(ctx sdk.Context, am AccountMapper, addrs []sdk.AccAddress) (accs []Account, res sdk.Result) { | ||
accs = make([]Account, len(addrs)) | ||
for i := 0; i < len(accs); i++ { | ||
accs[i] = am.GetAccount(ctx, addrs[i]) | ||
if accs[i] == nil { | ||
return nil, sdk.ErrUnknownAddress(addrs[i].String()).Result() | ||
} | ||
} | ||
return | ||
} | ||
|
||
accnum := acc.GetAccountNumber() | ||
seq := acc.GetSequence() | ||
func validateAccNumAndSequence(accs []Account, sigs []StdSignature) sdk.Result { | ||
for i := 0; i < len(accs); i++ { | ||
accnum := accs[i].GetAccountNumber() | ||
seq := accs[i].GetSequence() | ||
// Check account number. | ||
if accnum != sigs[i].AccountNumber { | ||
return sdk.ErrInvalidSequence( | ||
fmt.Sprintf("Invalid account number. Got %d, expected %d", sigs[i].AccountNumber, accnum)).Result() | ||
} | ||
|
||
// Check account number. | ||
if accnum != sig.AccountNumber { | ||
return nil, sdk.ErrInvalidSequence( | ||
fmt.Sprintf("Invalid account number. Got %d, expected %d", sig.AccountNumber, accnum)).Result() | ||
// Check sequence number. | ||
if seq != sigs[i].Sequence { | ||
return sdk.ErrInvalidSequence( | ||
fmt.Sprintf("Invalid sequence. Got %d, expected %d", sigs[i].Sequence, seq)).Result() | ||
} | ||
} | ||
return sdk.Result{} | ||
} | ||
|
||
// Check sequence number. | ||
if seq != sig.Sequence { | ||
return nil, sdk.ErrInvalidSequence( | ||
fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result() | ||
} | ||
err := acc.SetSequence(seq + 1) | ||
if err != nil { | ||
// Handle w/ #870 | ||
panic(err) | ||
} | ||
// verify the signature and increment the sequence. | ||
// if the account doesn't have a pubkey, set it. | ||
func processSig(ctx sdk.Context, | ||
acc Account, sig StdSignature, signBytes []byte, simulate bool) (updatedAcc Account, res sdk.Result) { | ||
pubKey, res := processPubKey(acc, sig, simulate) | ||
if !res.IsOK() { | ||
return nil, res | ||
} | ||
err = acc.SetPubKey(pubKey) | ||
err := acc.SetPubKey(pubKey) | ||
if err != nil { | ||
return nil, sdk.ErrInternal("setting PubKey on signer's account").Result() | ||
} | ||
|
@@ -196,7 +185,14 @@ func processSig( | |
return nil, sdk.ErrUnauthorized("signature verification failed").Result() | ||
} | ||
|
||
return | ||
// increment the sequence number | ||
err = acc.SetSequence(acc.GetSequence() + 1) | ||
if err != nil { | ||
// Handle w/ #870 | ||
panic(err) | ||
} | ||
|
||
return acc, res | ||
} | ||
|
||
var dummySecp256k1Pubkey secp256k1.PubKeySecp256k1 | ||
|
@@ -245,8 +241,9 @@ func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) { | |
} | ||
|
||
func adjustFeesByGas(fees sdk.Coins, gas int64) sdk.Coins { | ||
gasCost := int64(float64(gas) * feeDeductionGasFactor) | ||
gasCost := int64(float64(gas) * gasPrice) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can't use float64(). |
||
gasFees := make(sdk.Coins, len(fees)) | ||
// TODO: Make this not price all coins in the same way | ||
for i := 0; i < len(fees); i++ { | ||
gasFees[i] = sdk.NewInt64Coin(fees[i].Denom, gasCost) | ||
} | ||
|
@@ -273,5 +270,37 @@ func deductFees(acc Account, fee StdFee) (Account, sdk.Result) { | |
return acc, sdk.Result{} | ||
} | ||
|
||
func ensureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result { | ||
// currently we use a very primitive gas pricing model with a constant gasPrice. | ||
// adjustFeesByGas handles calculating the amount of fees required based on the provided gas. | ||
// TODO: Make the gasPrice not a constant, and account for tx size. | ||
requiredFees := adjustFeesByGas(ctx.MinimumFees(), stdTx.Fee.Gas) | ||
|
||
if !ctx.MinimumFees().IsZero() && stdTx.Fee.Amount.IsLT(requiredFees) { | ||
// validators reject any tx from the mempool with less than the minimum fee per gas * gas factor | ||
return sdk.ErrInsufficientFee(fmt.Sprintf( | ||
"insufficient fee, got: %q required: %q", stdTx.Fee.Amount, requiredFees)).Result() | ||
} | ||
return sdk.Result{} | ||
} | ||
|
||
func setGasMeter(simulate bool, ctx sdk.Context, stdTx StdTx) sdk.Context { | ||
// set the gas meter | ||
if simulate { | ||
return ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) | ||
} | ||
return ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) | ||
} | ||
|
||
func getSignBytesList(chainID string, stdTx StdTx, stdSigs []StdSignature) (signatureBytesList [][]byte) { | ||
signatureBytesList = make([][]byte, len(stdSigs)) | ||
for i := 0; i < len(stdSigs); i++ { | ||
signatureBytesList[i] = StdSignBytes(chainID, | ||
stdSigs[i].AccountNumber, stdSigs[i].Sequence, | ||
stdTx.Fee, stdTx.Msgs, stdTx.Memo) | ||
} | ||
return | ||
} | ||
|
||
// BurnFeeHandler burns all fees (decreasing total supply) | ||
func BurnFeeHandler(_ sdk.Context, _ sdk.Tx, _ sdk.Coins) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to setGasMeter method.