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

Add FeeAccount to StdTx and add fee-delegation module #4616

Closed
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions client/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const (
FlagRPCWriteTimeout = "write-timeout"
FlagOutputDocument = "output-document" // inspired by wget -O
FlagSkipConfirmation = "yes"
FlagFeeAccount = "fee-account"
)

// LineBreak can be included in a command list to provide a blank line
Expand Down Expand Up @@ -99,6 +100,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it")
c.Flags().Bool(FlagGenerateOnly, false, "Build an unsigned transaction and write it to STDOUT (when enabled, the local Keybase is not accessible and the node operates offline)")
c.Flags().BoolP(FlagSkipConfirmation, "y", false, "Skip tx broadcasting prompt confirmation")
c.Flags().String(FlagFeeAccount, "", "Set a fee account to pay fess with if they have been delegated by this account")

// --gas can accept integers and "simulate"
c.Flags().Var(&GasFlagVar, "gas", fmt.Sprintf(
Expand Down
2 changes: 1 addition & 1 deletion simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func NewSimApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bo
// initialize BaseApp
app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.supplyKeeper, auth.DefaultSigVerificationGasConsumer))
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.supplyKeeper, nil, auth.DefaultSigVerificationGasConsumer))
app.SetEndBlocker(app.EndBlocker)

if loadLatest {
Expand Down
42 changes: 28 additions & 14 deletions x/auth/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,16 @@ func init() {
// and also to accept or reject different types of PubKey's. This is where apps can define their own PubKey
type SignatureVerificationGasConsumer = func(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params Params) sdk.Result

type FeeDelegationHandler interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we add a godoc to FeeDelegationHandler?

// AllowDelegatedFees checks if the grantee can use the granter's account to spend the specified fees, updating
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we wrap the comments at 80 chars (if not already)?

// any fee allowance in accordance with the provided fees
AllowDelegatedFees(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccAddress, fee sdk.Coins) bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
AllowDelegatedFees(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccAddress, fee sdk.Coins) bool
AllowDelegatedFees(ctx sdk.Context, grantee, granter sdk.AccAddress, fee sdk.Coins) bool

}

// NewAnteHandler returns an AnteHandler that checks and increments sequence
Copy link
Contributor

Choose a reason for hiding this comment

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

Lets update the godoc to now mention the use of the new fee delegation logic.

// numbers, checks signatures & account numbers, and deducts fees from the first
// signer.
func NewAnteHandler(ak AccountKeeper, supplyKeeper types.SupplyKeeper, sigGasConsumer SignatureVerificationGasConsumer) sdk.AnteHandler {
func NewAnteHandler(ak AccountKeeper, supplyKeeper types.SupplyKeeper, feeDelegationHandler FeeDelegationHandler, sigGasConsumer SignatureVerificationGasConsumer) sdk.AnteHandler {
return func(
ctx sdk.Context, tx sdk.Tx, simulate bool,
) (newCtx sdk.Context, res sdk.Result, abort bool) {
Expand Down Expand Up @@ -110,34 +116,42 @@ func NewAnteHandler(ak AccountKeeper, supplyKeeper types.SupplyKeeper, sigGasCon
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])
feeAddr := stdTx.FeeAccount
if len(feeAddr) != 0 {
// check if fees can be delegated
if feeDelegationHandler == nil {
return newCtx, sdk.ErrUnknownRequest("delegated fees aren't supported").Result(), true
}
if !feeDelegationHandler.AllowDelegatedFees(ctx, signerAddrs[0], feeAddr, stdTx.Fee.Amount) {
return newCtx, res, true
}
} else {
// use first signer, who's going to pay the fees
feeAddr = signerAddrs[0]
}

// pay fees with the fee account
feeAccount, res := GetSignerAcc(newCtx, ak, feeAddr)
if !res.IsOK() {
return newCtx, res, true
}

// deduct the fees
if !stdTx.Fee.Amount.IsZero() {
res = DeductFees(supplyKeeper, newCtx, signerAccs[0], stdTx.Fee.Amount)
res = DeductFees(supplyKeeper, newCtx, feeAccount, stdTx.Fee.Amount)
if !res.IsOK() {
return newCtx, res, true
}

// reload the account as fees have been deducted
signerAccs[0] = ak.GetAccount(newCtx, signerAccs[0].GetAddress())
}

// 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
}
signerAccs[i], res = GetSignerAcc(newCtx, ak, signerAddrs[i])
if !res.IsOK() {
return newCtx, res, true
}

// check signature, return account with incremented nonce
Expand Down Expand Up @@ -417,6 +431,6 @@ func GetSignBytes(chainID string, stdTx StdTx, acc Account, genesis bool) []byte
}

return StdSignBytes(
chainID, accNum, acc.GetSequence(), stdTx.Fee, stdTx.Msgs, stdTx.Memo,
chainID, accNum, acc.GetSequence(), stdTx.Fee, stdTx.Msgs, stdTx.Memo, stdTx.FeeAccount,
)
}
24 changes: 12 additions & 12 deletions x/auth/ante_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestAnteHandlerSigErrors(t *testing.T) {
// setup
input := setupTestInput()
ctx := input.ctx
anteHandler := NewAnteHandler(input.ak, input.sk, DefaultSigVerificationGasConsumer)
anteHandler := NewAnteHandler(input.ak, input.sk, nil, DefaultSigVerificationGasConsumer)

// keys and addresses
priv1, _, addr1 := types.KeyTestPubAddr()
Expand Down Expand Up @@ -97,7 +97,7 @@ func TestAnteHandlerSigErrors(t *testing.T) {
func TestAnteHandlerAccountNumbers(t *testing.T) {
// setup
input := setupTestInput()
anteHandler := NewAnteHandler(input.ak, input.sk, DefaultSigVerificationGasConsumer)
anteHandler := NewAnteHandler(input.ak, input.sk, nil, DefaultSigVerificationGasConsumer)
ctx := input.ctx.WithBlockHeight(1)

// keys and addresses
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestAnteHandlerAccountNumbers(t *testing.T) {
func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) {
// setup
input := setupTestInput()
anteHandler := NewAnteHandler(input.ak, input.sk, DefaultSigVerificationGasConsumer)
anteHandler := NewAnteHandler(input.ak, input.sk, nil, DefaultSigVerificationGasConsumer)
ctx := input.ctx.WithBlockHeight(0)

// keys and addresses
Expand Down Expand Up @@ -210,7 +210,7 @@ func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) {
func TestAnteHandlerSequences(t *testing.T) {
// setup
input := setupTestInput()
anteHandler := NewAnteHandler(input.ak, input.sk, DefaultSigVerificationGasConsumer)
anteHandler := NewAnteHandler(input.ak, input.sk, nil, DefaultSigVerificationGasConsumer)
ctx := input.ctx.WithBlockHeight(1)

// keys and addresses
Expand Down Expand Up @@ -288,7 +288,7 @@ func TestAnteHandlerFees(t *testing.T) {
// setup
input := setupTestInput()
ctx := input.ctx
anteHandler := NewAnteHandler(input.ak, input.sk, DefaultSigVerificationGasConsumer)
anteHandler := NewAnteHandler(input.ak, input.sk, nil, DefaultSigVerificationGasConsumer)

// keys and addresses
priv1, _, addr1 := types.KeyTestPubAddr()
Expand Down Expand Up @@ -327,7 +327,7 @@ func TestAnteHandlerFees(t *testing.T) {
func TestAnteHandlerMemoGas(t *testing.T) {
// setup
input := setupTestInput()
anteHandler := NewAnteHandler(input.ak, input.sk, DefaultSigVerificationGasConsumer)
anteHandler := NewAnteHandler(input.ak, input.sk, nil, DefaultSigVerificationGasConsumer)
ctx := input.ctx.WithBlockHeight(1)

// keys and addresses
Expand Down Expand Up @@ -367,7 +367,7 @@ func TestAnteHandlerMemoGas(t *testing.T) {
func TestAnteHandlerMultiSigner(t *testing.T) {
// setup
input := setupTestInput()
anteHandler := NewAnteHandler(input.ak, input.sk, DefaultSigVerificationGasConsumer)
anteHandler := NewAnteHandler(input.ak, input.sk, nil, DefaultSigVerificationGasConsumer)
ctx := input.ctx.WithBlockHeight(1)

// keys and addresses
Expand Down Expand Up @@ -417,7 +417,7 @@ func TestAnteHandlerMultiSigner(t *testing.T) {
func TestAnteHandlerBadSignBytes(t *testing.T) {
// setup
input := setupTestInput()
anteHandler := NewAnteHandler(input.ak, input.sk, DefaultSigVerificationGasConsumer)
anteHandler := NewAnteHandler(input.ak, input.sk, nil, DefaultSigVerificationGasConsumer)
ctx := input.ctx.WithBlockHeight(1)

// keys and addresses
Expand Down Expand Up @@ -472,7 +472,7 @@ func TestAnteHandlerBadSignBytes(t *testing.T) {
for _, cs := range cases {
tx := types.NewTestTxWithSignBytes(
msgs, privs, accnums, seqs, fee,
StdSignBytes(cs.chainID, cs.accnum, cs.seq, cs.fee, cs.msgs, ""),
StdSignBytes(cs.chainID, cs.accnum, cs.seq, cs.fee, cs.msgs, "", nil),
"",
)
checkInvalidTx(t, anteHandler, ctx, tx, false, cs.code)
Expand All @@ -494,7 +494,7 @@ func TestAnteHandlerBadSignBytes(t *testing.T) {
func TestAnteHandlerSetPubKey(t *testing.T) {
// setup
input := setupTestInput()
anteHandler := NewAnteHandler(input.ak, input.sk, DefaultSigVerificationGasConsumer)
anteHandler := NewAnteHandler(input.ak, input.sk, nil, DefaultSigVerificationGasConsumer)
ctx := input.ctx.WithBlockHeight(1)

// keys and addresses
Expand Down Expand Up @@ -690,7 +690,7 @@ func TestCountSubkeys(t *testing.T) {
func TestAnteHandlerSigLimitExceeded(t *testing.T) {
// setup
input := setupTestInput()
anteHandler := NewAnteHandler(input.ak, input.sk, DefaultSigVerificationGasConsumer)
anteHandler := NewAnteHandler(input.ak, input.sk, nil, DefaultSigVerificationGasConsumer)
ctx := input.ctx.WithBlockHeight(1)

// keys and addresses
Expand Down Expand Up @@ -780,7 +780,7 @@ func TestCustomSignatureVerificationGasConsumer(t *testing.T) {
// setup
input := setupTestInput()
// setup an ante handler that only accepts PubKeyEd25519
anteHandler := NewAnteHandler(input.ak, input.sk, func(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params Params) sdk.Result {
anteHandler := NewAnteHandler(input.ak, input.sk, nil, func(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params Params) sdk.Result {
switch pubkey := pubkey.(type) {
case ed25519.PubKeyEd25519:
meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519")
Expand Down
4 changes: 2 additions & 2 deletions x/auth/client/cli/tx_multisign.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func makeMultiSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string)
// Validate each signature
sigBytes := types.StdSignBytes(
txBldr.ChainID(), txBldr.AccountNumber(), txBldr.Sequence(),
stdTx.Fee, stdTx.GetMsgs(), stdTx.GetMemo(),
stdTx.Fee, stdTx.GetMsgs(), stdTx.GetMemo(), nil,
)
if ok := stdSig.PubKey.VerifyBytes(sigBytes, stdSig.Signature); !ok {
return fmt.Errorf("couldn't verify signature")
Expand All @@ -113,7 +113,7 @@ func makeMultiSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string)
}

newStdSig := types.StdSignature{Signature: cdc.MustMarshalBinaryBare(multisigSig), PubKey: multisigPub}
newTx := types.NewStdTx(stdTx.GetMsgs(), stdTx.Fee, []types.StdSignature{newStdSig}, stdTx.GetMemo())
newTx := types.NewStdTx(stdTx.GetMsgs(), stdTx.Fee, []types.StdSignature{newStdSig}, stdTx.GetMemo(), nil)

sigOnly := viper.GetBool(flagSigOnly)
var json []byte
Expand Down
2 changes: 1 addition & 1 deletion x/auth/client/cli/tx_sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func printAndValidateSigs(

sigBytes := types.StdSignBytes(
chainID, acc.GetAccountNumber(), acc.GetSequence(),
stdTx.Fee, stdTx.GetMsgs(), stdTx.GetMemo(),
stdTx.Fee, stdTx.GetMsgs(), stdTx.GetMemo(), stdTx.FeeAccount,
)

if ok := sig.VerifyBytes(sigBytes, sig.Signature); !ok {
Expand Down
2 changes: 1 addition & 1 deletion x/auth/client/utils/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func WriteGenerateStdTxResponse(w http.ResponseWriter, cliCtx context.CLIContext
return
}

output, err := cliCtx.Codec.MarshalJSON(types.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo))
output, err := cliCtx.Codec.MarshalJSON(types.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo, stdMsg.FeeAccount))
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
Expand Down
13 changes: 12 additions & 1 deletion x/auth/client/utils/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ func CompleteAndBroadcastTxCLI(txBldr authtypes.TxBuilder, cliCtx context.CLICon
return nil
}

// support delegated fee payments
feeAccount := viper.GetString(flags.FlagFeeAccount)
if len(feeAccount) != 0 {
feeAcccountAddr, err := sdk.AccAddressFromBech32(feeAccount)
if err != nil {
return err
}
txBldr.WithFeeAccount(feeAcccountAddr)
Copy link
Contributor

Choose a reason for hiding this comment

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

gofmt

}


if !cliCtx.SkipConfirm {
stdSignMsg, err := txBldr.BuildSignMsg(msgs)
if err != nil {
Expand Down Expand Up @@ -344,7 +355,7 @@ func buildUnsignedStdTxOffline(txBldr authtypes.TxBuilder, cliCtx context.CLICon
return stdTx, nil
}

return authtypes.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo), nil
return authtypes.NewStdTx(stdSignMsg.Msgs, stdSignMsg.Fee, nil, stdSignMsg.Memo, stdSignMsg.FeeAccount), nil
}

func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool {
Expand Down
4 changes: 2 additions & 2 deletions x/auth/client/utils/tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestReadStdTxFromFile(t *testing.T) {

// Build a test transaction
fee := authtypes.NewStdFee(50000, sdk.Coins{sdk.NewInt64Coin("atom", 150)})
stdTx := authtypes.NewStdTx([]sdk.Msg{}, fee, []authtypes.StdSignature{}, "foomemo")
stdTx := authtypes.NewStdTx([]sdk.Msg{}, fee, []authtypes.StdSignature{}, "foomemo", nil)

// Write it to the file
encodedTx, _ := cdc.MarshalJSON(stdTx)
Expand All @@ -113,7 +113,7 @@ func TestReadStdTxFromFile(t *testing.T) {

func compareEncoders(t *testing.T, expected sdk.TxEncoder, actual sdk.TxEncoder) {
msgs := []sdk.Msg{sdk.NewTestMsg(addr)}
tx := authtypes.NewStdTx(msgs, authtypes.StdFee{}, []authtypes.StdSignature{}, "")
tx := authtypes.NewStdTx(msgs, authtypes.StdFee{}, []authtypes.StdSignature{}, "", nil)

defaultEncoderBytes, err := expected(tx)
require.NoError(t, err)
Expand Down
15 changes: 8 additions & 7 deletions x/auth/types/stdsignmsg.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ import (
// a Msg with the other requirements for a StdSignDoc before
// it is signed. For use in the CLI.
type StdSignMsg struct {
ChainID string `json:"chain_id"`
AccountNumber uint64 `json:"account_number"`
Sequence uint64 `json:"sequence"`
Fee StdFee `json:"fee"`
Msgs []sdk.Msg `json:"msgs"`
Memo string `json:"memo"`
ChainID string `json:"chain_id"`
AccountNumber uint64 `json:"account_number"`
Sequence uint64 `json:"sequence"`
Fee StdFee `json:"fee"`
Msgs []sdk.Msg `json:"msgs"`
Memo string `json:"memo"`
FeeAccount sdk.AccAddress `json:"fee_account"`
}

// get message bytes
func (msg StdSignMsg) Bytes() []byte {
return StdSignBytes(msg.ChainID, msg.AccountNumber, msg.Sequence, msg.Fee, msg.Msgs, msg.Memo)
return StdSignBytes(msg.ChainID, msg.AccountNumber, msg.Sequence, msg.Fee, msg.Msgs, msg.Memo, msg.FeeAccount)
}
12 changes: 9 additions & 3 deletions x/auth/types/stdtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@ type StdTx struct {
Fee StdFee `json:"fee"`
Signatures []StdSignature `json:"signatures"`
Memo string `json:"memo"`
// FeeAccount is an optional account that fees can be spent from if such
// delegation is enabled
FeeAccount sdk.AccAddress `json:"fee_account"`
}

func NewStdTx(msgs []sdk.Msg, fee StdFee, sigs []StdSignature, memo string) StdTx {
func NewStdTx(msgs []sdk.Msg, fee StdFee, sigs []StdSignature, memo string, feeAccount sdk.AccAddress) StdTx {
return StdTx{
Msgs: msgs,
Fee: fee,
Signatures: sigs,
Memo: memo,
FeeAccount: feeAccount,
}
}

Expand Down Expand Up @@ -163,10 +167,11 @@ type StdSignDoc struct {
Memo string `json:"memo"`
Msgs []json.RawMessage `json:"msgs"`
Sequence uint64 `json:"sequence"`
FeeAccount []byte `json:"fee_account"`
}

// StdSignBytes returns the bytes to sign for a transaction.
func StdSignBytes(chainID string, accnum uint64, sequence uint64, fee StdFee, msgs []sdk.Msg, memo string) []byte {
func StdSignBytes(chainID string, accnum uint64, sequence uint64, fee StdFee, msgs []sdk.Msg, memo string, feeAccount sdk.AccAddress) []byte {
var msgsBytes []json.RawMessage
for _, msg := range msgs {
msgsBytes = append(msgsBytes, json.RawMessage(msg.GetSignBytes()))
Expand All @@ -178,6 +183,7 @@ func StdSignBytes(chainID string, accnum uint64, sequence uint64, fee StdFee, ms
Memo: memo,
Msgs: msgsBytes,
Sequence: sequence,
FeeAccount: feeAccount,
})
if err != nil {
panic(err)
Expand All @@ -188,7 +194,7 @@ func StdSignBytes(chainID string, accnum uint64, sequence uint64, fee StdFee, ms
// StdSignature represents a sig
type StdSignature struct {
crypto.PubKey `json:"pub_key"` // optional
Signature []byte `json:"signature"`
Signature []byte `json:"signature"`
}

// DefaultTxDecoder logic for standard transaction decoding
Expand Down
Loading