cosmos-sdk/x/auth/ante.go

384 lines
12 KiB
Go

package auth
import (
"bytes"
"encoding/hex"
"fmt"
"strings"
"time"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/multisig"
"github.com/tendermint/tendermint/crypto/secp256k1"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
var (
// simulation signature values used to estimate gas consumption
simSecp256k1Pubkey secp256k1.PubKeySecp256k1
simSecp256k1Sig [64]byte
)
func init() {
// This decodes a valid hex string into a sepc256k1Pubkey for use in transaction simulation
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
copy(simSecp256k1Pubkey[:], bz)
}
// NewAnteHandler returns an AnteHandler that checks and increments sequence
// numbers, checks signatures & account numbers, and deducts fees from the first
// signer.
func NewAnteHandler(ak AccountKeeper, fck FeeCollectionKeeper) sdk.AnteHandler {
return func(
ctx sdk.Context, tx sdk.Tx, simulate bool,
) (newCtx sdk.Context, res sdk.Result, abort bool) {
// all transactions must be of type auth.StdTx
stdTx, ok := tx.(StdTx)
if !ok {
// Set a gas meter with limit 0 as to prevent an infinite gas meter attack
// during runTx.
newCtx = SetGasMeter(simulate, ctx, 0)
return newCtx, sdk.ErrInternal("tx must be StdTx").Result(), true
}
params := ak.GetParams(ctx)
// 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.Fee)
if !res.IsOK() {
return newCtx, res, true
}
}
newCtx = SetGasMeter(simulate, ctx, stdTx.Fee.Gas)
// 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.
defer func() {
if r := recover(); r != nil {
switch rType := r.(type) {
case sdk.ErrorOutOfGas:
log := fmt.Sprintf(
"out of gas in location: %v; gasWanted: %d, gasUsed: %d",
rType.Descriptor, stdTx.Fee.Gas, newCtx.GasMeter().GasConsumed(),
)
res = sdk.ErrOutOfGas(log).Result()
res.GasWanted = stdTx.Fee.Gas
res.GasUsed = newCtx.GasMeter().GasConsumed()
abort = true
default:
panic(r)
}
}
}()
if err := tx.ValidateBasic(); err != nil {
return newCtx, err.Result(), true
}
newCtx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*sdk.Gas(len(newCtx.TxBytes())), "txSize")
if res := ValidateMemo(stdTx, params); !res.IsOK() {
return newCtx, res, true
}
// stdSigs contains the sequence number, account number, and signatures.
// When simulating, this would just be a 0-length slice.
signerAddrs := stdTx.GetSigners()
signerAccs := make([]Account, len(signerAddrs))
isGenesis := ctx.BlockHeight() == 0
// fetch first signer, who's going to pay the fees
signerAccs[0], res = GetSignerAcc(newCtx, ak, signerAddrs[0])
if !res.IsOK() {
return newCtx, res, true
}
if !stdTx.Fee.Amount.IsZero() {
signerAccs[0], res = DeductFees(ctx.BlockHeader().Time, signerAccs[0], stdTx.Fee)
if !res.IsOK() {
return newCtx, res, true
}
fck.AddCollectedFees(newCtx, stdTx.Fee.Amount)
}
// stdSigs contains the sequence number, account number, and signatures.
// When simulating, this would just be a 0-length slice.
stdSigs := stdTx.GetSignatures()
for i := 0; i < len(stdSigs); i++ {
// skip the fee payer, account is cached and fees were deducted already
if i != 0 {
signerAccs[i], res = GetSignerAcc(newCtx, ak, signerAddrs[i])
if !res.IsOK() {
return newCtx, res, true
}
}
// check signature, return account with incremented nonce
signBytes := GetSignBytes(newCtx.ChainID(), stdTx, signerAccs[i], isGenesis)
signerAccs[i], res = processSig(newCtx, signerAccs[i], stdSigs[i], signBytes, simulate, params)
if !res.IsOK() {
return newCtx, res, true
}
ak.SetAccount(newCtx, signerAccs[i])
}
// TODO: tx tags (?)
return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false // continue...
}
}
// GetSignerAcc returns an account for a given address that is expected to sign
// a transaction.
func GetSignerAcc(ctx sdk.Context, ak AccountKeeper, addr sdk.AccAddress) (Account, sdk.Result) {
if acc := ak.GetAccount(ctx, addr); acc != nil {
return acc, sdk.Result{}
}
return nil, sdk.ErrUnknownAddress(addr.String()).Result()
}
// ValidateMemo validates the memo size.
func ValidateMemo(stdTx StdTx, params Params) sdk.Result {
memoLength := len(stdTx.GetMemo())
if uint64(memoLength) > params.MaxMemoCharacters {
return sdk.ErrMemoTooLarge(
fmt.Sprintf(
"maximum number of characters is %d but received %d characters",
params.MaxMemoCharacters, memoLength,
),
).Result()
}
return sdk.Result{}
}
// 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, params Params,
) (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()
}
if simulate {
// Simulated txs should not contain a signature and are not required to
// contain a pubkey, so we must account for tx size of including a
// StdSignature (Amino encoding) and simulate gas consumption
// (assuming a SECP256k1 simulation key).
consumeSimSigGas(ctx.GasMeter(), pubKey, sig, params)
}
consumeSigVerificationGas(ctx.GasMeter(), sig.Signature, pubKey, params)
if !simulate && !pubKey.VerifyBytes(signBytes, sig.Signature) {
return nil, sdk.ErrUnauthorized("signature verification failed").Result()
}
if err := acc.SetSequence(acc.GetSequence() + 1); err != nil {
panic(err)
}
return acc, res
}
func consumeSimSigGas(gasmeter sdk.GasMeter, pubkey crypto.PubKey, sig StdSignature, params Params) {
simSig := StdSignature{PubKey: pubkey}
if len(sig.Signature) == 0 {
simSig.Signature = simSecp256k1Sig[:]
}
sigBz := msgCdc.MustMarshalBinaryLengthPrefixed(simSig)
cost := sdk.Gas(len(sigBz) + 6)
// If the pubkey is a multi-signature pubkey, then we estimate for the maximum
// number of signers.
if _, ok := pubkey.(multisig.PubKeyMultisigThreshold); ok {
cost *= params.TxSigLimit
}
gasmeter.ConsumeGas(params.TxSizeCostPerByte*cost, "txSize")
}
// ProcessPubKey verifies that the given account address matches that of the
// StdSignature. In addition, it will set the public key of the account if it
// has not been set.
func ProcessPubKey(acc Account, sig StdSignature, simulate bool) (crypto.PubKey, sdk.Result) {
// If pubkey is not known for account, set it from the StdSignature.
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 simSecp256k1Pubkey, sdk.Result{}
}
return pubKey, sdk.Result{}
}
if pubKey == nil {
pubKey = sig.PubKey
if pubKey == nil {
return nil, sdk.ErrInvalidPubKey("PubKey not found").Result()
}
if !bytes.Equal(pubKey.Address(), acc.GetAddress()) {
return nil, sdk.ErrInvalidPubKey(
fmt.Sprintf("PubKey does not match Signer address %s", acc.GetAddress())).Result()
}
}
return pubKey, sdk.Result{}
}
// consumeSigVerificationGas consumes gas for signature verification based upon
// the public key type. The cost is fetched from the given params and is matched
// by the concrete type.
//
// TODO: Design a cleaner and flexible way to match concrete public key types.
func consumeSigVerificationGas(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params Params) {
pubkeyType := strings.ToLower(fmt.Sprintf("%T", pubkey))
switch {
case strings.Contains(pubkeyType, "ed25519"):
meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519")
case strings.Contains(pubkeyType, "secp256k1"):
meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1")
case strings.Contains(pubkeyType, "multisigthreshold"):
var multisignature multisig.Multisignature
codec.Cdc.MustUnmarshalBinaryBare(sig, &multisignature)
multisigPubKey := pubkey.(multisig.PubKeyMultisigThreshold)
consumeMultisignatureVerificationGas(meter, multisignature, multisigPubKey, params)
default:
panic(fmt.Sprintf("unrecognized signature type: %s", pubkeyType))
}
}
func consumeMultisignatureVerificationGas(meter sdk.GasMeter,
sig multisig.Multisignature, pubkey multisig.PubKeyMultisigThreshold,
params Params) {
size := sig.BitArray.Size()
sigIndex := 0
for i := 0; i < size; i++ {
if sig.BitArray.GetIndex(i) {
consumeSigVerificationGas(meter, sig.Sigs[sigIndex], pubkey.PubKeys[i], params)
sigIndex++
}
}
}
// DeductFees deducts fees from the given account.
//
// NOTE: We could use the CoinKeeper (in addition to the AccountKeeper, because
// the CoinKeeper doesn't give us accounts), but it seems easier to do this.
func DeductFees(blockTime time.Time, acc Account, fee StdFee) (Account, sdk.Result) {
coins := acc.GetCoins()
feeAmount := fee.Amount
if !feeAmount.IsValid() {
return nil, sdk.ErrInsufficientFee(fmt.Sprintf("invalid fee amount: %s", feeAmount)).Result()
}
// get the resulting coins deducting the fees
newCoins, ok := coins.SafeMinus(feeAmount)
if ok {
return nil, sdk.ErrInsufficientFunds(
fmt.Sprintf("insufficient funds to pay for fees; %s < %s", coins, feeAmount),
).Result()
}
// Validate the account has enough "spendable" coins as this will cover cases
// such as vesting accounts.
spendableCoins := acc.SpendableCoins(blockTime)
if _, hasNeg := spendableCoins.SafeMinus(feeAmount); hasNeg {
return nil, sdk.ErrInsufficientFunds(
fmt.Sprintf("insufficient funds to pay for fees; %s < %s", spendableCoins, feeAmount),
).Result()
}
if err := acc.SetCoins(newCoins); err != nil {
return nil, sdk.ErrInternal(err.Error()).Result()
}
return acc, sdk.Result{}
}
// EnsureSufficientMempoolFees verifies that the given transaction has supplied
// enough fees to cover a proposer's minimum fees. A result object is returned
// indicating success or failure.
//
// Contract: This should only be called during CheckTx as it cannot be part of
// consensus.
func EnsureSufficientMempoolFees(ctx sdk.Context, stdFee StdFee) sdk.Result {
minGasPrices := ctx.MinGasPrices()
if !minGasPrices.IsZero() {
requiredFees := make(sdk.Coins, len(minGasPrices))
// Determine the required fees by multiplying each required minimum gas
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit).
glDec := sdk.NewDec(int64(stdFee.Gas))
for i, gp := range minGasPrices {
fee := gp.Amount.Mul(glDec)
requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
}
if !stdFee.Amount.IsAnyGTE(requiredFees) {
return sdk.ErrInsufficientFee(
fmt.Sprintf(
"insufficient fees; got: %q required: %q", stdFee.Amount, requiredFees,
),
).Result()
}
}
return sdk.Result{}
}
// SetGasMeter returns a new context with a gas meter set from a given context.
func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64) 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(gasLimit))
}
// GetSignBytes returns a slice of bytes to sign over for a given transaction
// and an account.
func GetSignBytes(chainID string, stdTx StdTx, acc Account, genesis bool) []byte {
var accNum uint64
if !genesis {
accNum = acc.GetAccountNumber()
}
return StdSignBytes(
chainID, accNum, acc.GetSequence(), stdTx.Fee, stdTx.Msgs, stdTx.Memo,
)
}