cosmos-sdk/x/auth/ante.go

295 lines
8.6 KiB
Go
Raw Normal View History

package auth
import (
2018-03-17 11:54:18 -07:00
"bytes"
"encoding/hex"
2018-03-12 17:40:04 -07:00
"fmt"
2018-01-12 14:30:02 -08:00
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/secp256k1"
)
2018-05-15 06:02:54 -07:00
const (
memoCostPerByte sdk.Gas = 3
ed25519VerifyCost = 590
secp256k1VerifyCost = 1000
maxMemoCharacters = 256
// how much gas = 1 atom
gasPerUnitCost = 10000
// max total number of sigs per tx
txSigLimit = 7
2018-05-15 06:02:54 -07:00
)
// NewAnteHandler returns an AnteHandler that checks and increments sequence
// numbers, checks signatures & account numbers, and deducts fees from the first
// signer.
func NewAnteHandler(am AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler {
return func(
ctx sdk.Context, tx sdk.Tx, simulate bool,
2018-07-13 10:53:12 -07:00
) (newCtx sdk.Context, res sdk.Result, abort bool) {
// all transactions must be of type auth.StdTx
2018-05-23 19:26:54 -07:00
stdTx, ok := tx.(StdTx)
if !ok {
2018-05-23 22:09:01 -07:00
return ctx, sdk.ErrInternal("tx must be StdTx").Result(), true
2018-05-23 19:26:54 -07:00
}
// Ensure that the provided fees meet a minimum threshold for the validator,
// if this is a CheckTx. This is only for local mempool purposes, and thus
// is only ran on check tx.
if ctx.IsCheckTx() && !simulate {
res := ensureSufficientMempoolFees(ctx, stdTx)
if !res.IsOK() {
return newCtx, res, true
}
}
2018-07-13 10:53:12 -07:00
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, but if it panics the context won't be set properly in
// runTx's recover call.
2018-07-12 19:38:22 -07:00
defer func() {
if r := recover(); r != nil {
switch rType := r.(type) {
case sdk.ErrorOutOfGas:
log := fmt.Sprintf("out of gas in location: %v", rType.Descriptor)
res = sdk.ErrOutOfGas(log).Result()
2018-07-13 10:53:12 -07:00
res.GasWanted = stdTx.Fee.Gas
res.GasUsed = newCtx.GasMeter().GasConsumed()
abort = true
2018-07-12 19:38:22 -07:00
default:
panic(r)
}
}
}()
if err := tx.ValidateBasic(); err != nil {
2018-07-13 10:53:12 -07:00
return newCtx, err.Result(), true
}
2018-07-13 10:53:12 -07:00
newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo")
// stdSigs contains the sequence number, account number, and signatures.
// When simulating, this would just be a 0-length slice.
stdSigs := stdTx.GetSignatures()
signerAddrs := stdTx.GetSigners()
signerAccs, res := getSignerAccs(newCtx, am, signerAddrs)
if !res.IsOK() {
return newCtx, res, true
}
isGenesis := ctx.BlockHeight() == 0
signBytesList := getSignBytesList(newCtx.ChainID(), stdTx, signerAccs, isGenesis)
2018-03-17 11:54:18 -07:00
// first sig pays the fees
if !stdTx.Fee.Amount.IsZero() {
signerAccs[0], res = deductFees(signerAccs[0], stdTx.Fee)
2018-03-03 23:32:39 -08:00
if !res.IsOK() {
2018-07-13 10:53:12 -07:00
return newCtx, res, true
}
2018-10-19 11:36:00 -07:00
fck.AddCollectedFees(newCtx, stdTx.Fee.Amount)
}
2018-03-17 11:54:18 -07:00
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
2018-03-17 11:54:18 -07:00
}
am.SetAccount(newCtx, signerAccs[i])
}
2018-03-17 11:54:18 -07:00
// cache the signer accounts in the context
2018-07-13 10:53:12 -07:00
newCtx = WithSigners(newCtx, signerAccs)
2018-03-17 11:54:18 -07:00
2018-03-14 10:16:52 -07:00
// TODO: tx tags (?)
2018-07-13 10:53:12 -07:00
return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false // continue...
}
}
2018-03-03 23:32:39 -08:00
func getSignerAccs(ctx sdk.Context, am AccountKeeper, 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()
}
2018-03-03 23:32:39 -08:00
}
return
}
2018-03-03 23:32:39 -08:00
// 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)
if err != nil {
return nil, sdk.ErrInternal("setting PubKey on signer's account").Result()
}
consumeSignatureVerificationGas(ctx.GasMeter(), pubKey)
if !simulate && !pubKey.VerifyBytes(signBytes, sig.Signature) {
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
}
err = acc.SetSequence(acc.GetSequence() + 1)
if err != nil {
// Handle w/ #870
panic(err)
}
return acc, res
}
var dummySecp256k1Pubkey secp256k1.PubKeySecp256k1
func init() {
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
copy(dummySecp256k1Pubkey[:], bz)
}
func processPubKey(acc Account, sig StdSignature, simulate bool) (crypto.PubKey, sdk.Result) {
// If pubkey is not known for account, set it from the StdSignature.
2018-03-03 23:32:39 -08:00
pubKey := acc.GetPubKey()
if simulate {
// In simulate mode the transaction comes with no signatures, thus if the
// account's pubkey is nil, both signature verification and gasKVStore.Set()
// shall consume the largest amount, i.e. it takes more gas to verify
// secp256k1 keys than ed25519 ones.
if pubKey == nil {
return dummySecp256k1Pubkey, sdk.Result{}
}
return pubKey, sdk.Result{}
}
2018-04-06 17:25:08 -07:00
if pubKey == nil {
2018-03-03 23:32:39 -08:00
pubKey = sig.PubKey
2018-04-06 17:25:08 -07:00
if pubKey == nil {
2018-03-17 11:54:18 -07:00
return nil, sdk.ErrInvalidPubKey("PubKey not found").Result()
}
if !bytes.Equal(pubKey.Address(), acc.GetAddress()) {
2018-03-17 11:54:18 -07:00
return nil, sdk.ErrInvalidPubKey(
fmt.Sprintf("PubKey does not match Signer address %v", acc.GetAddress())).Result()
2018-03-14 10:16:52 -07:00
}
2018-03-03 23:32:39 -08:00
}
return pubKey, sdk.Result{}
2018-03-17 11:54:18 -07:00
}
func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) {
switch pubkey.(type) {
case ed25519.PubKeyEd25519:
meter.ConsumeGas(ed25519VerifyCost, "ante verify: ed25519")
case secp256k1.PubKeySecp256k1:
meter.ConsumeGas(secp256k1VerifyCost, "ante verify: secp256k1")
default:
panic("Unrecognized signature type")
}
}
func adjustFeesByGas(fees sdk.Coins, gas uint64) sdk.Coins {
gasCost := gas / gasPerUnitCost
gasFees := make(sdk.Coins, len(fees))
// TODO: Make this not price all coins in the same way
// TODO: Undo int64 casting once unsigned integers are supported for coins
for i := 0; i < len(fees); i++ {
gasFees[i] = sdk.NewInt64Coin(fees[i].Denom, int64(gasCost))
}
return fees.Plus(gasFees)
}
2018-03-17 13:53:27 -07:00
// Deduct the fee from the account.
// We could use the CoinKeeper (in addition to the AccountKeeper,
2018-03-17 13:53:27 -07:00
// because the CoinKeeper doesn't give us accounts), but it seems easier to do this.
2018-05-23 19:26:54 -07:00
func deductFees(acc Account, fee StdFee) (Account, sdk.Result) {
2018-03-17 11:54:18 -07:00
coins := acc.GetCoins()
feeAmount := fee.Amount
2018-03-17 13:20:24 -07:00
if !feeAmount.IsValid() {
return nil, sdk.ErrInsufficientFee(fmt.Sprintf("invalid fee amount: %s", feeAmount)).Result()
}
newCoins, ok := coins.SafeMinus(feeAmount)
if ok {
2018-03-17 11:54:18 -07:00
errMsg := fmt.Sprintf("%s < %s", coins, feeAmount)
return nil, sdk.ErrInsufficientFunds(errMsg).Result()
2018-03-14 10:16:52 -07:00
}
err := acc.SetCoins(newCoins)
if err != nil {
// Handle w/ #870
panic(err)
}
2018-03-17 11:54:18 -07:00
return acc, sdk.Result{}
2018-03-03 23:32:39 -08:00
}
2018-04-16 16:06:07 -07:00
func ensureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result {
// Currently we use a very primitive gas pricing model with a constant
// gasPrice where 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.
// - Make Gas an unsigned integer and use tx basic validation
if stdTx.Fee.Gas <= 0 {
return sdk.ErrInternal(fmt.Sprintf("invalid gas supplied: %d", stdTx.Fee.Gas)).Result()
}
requiredFees := adjustFeesByGas(ctx.MinimumFees(), stdTx.Fee.Gas)
// NOTE: !A.IsAllGTE(B) is not the same as A.IsAllLT(B).
if !ctx.MinimumFees().IsZero() && !stdTx.Fee.Amount.IsAllGTE(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 {
// In various cases such as simulation and during the genesis block, we do not
// meter any gas utilization.
if simulate || ctx.BlockHeight() == 0 {
return ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
}
return ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas))
}
func getSignBytesList(chainID string, stdTx StdTx, accs []Account, genesis bool) (signatureBytesList [][]byte) {
signatureBytesList = make([][]byte, len(accs))
for i := 0; i < len(accs); i++ {
accNum := accs[i].GetAccountNumber()
if genesis {
accNum = 0
}
signatureBytesList[i] = StdSignBytes(chainID,
accNum, accs[i].GetSequence(),
stdTx.Fee, stdTx.Msgs, stdTx.Memo)
}
return
}