CAP: 0015
Title: Fee Bump Transaction
Author: OrbitLens <orbit.lens@gmail.com>
Status: Draft
Created: 2018-11-26
Updated: 2019-02-13
Discussion: https://groups.google.com/forum/#!topic/stellar-dev/ckzBRlfr2VU
Protocol version: TBD
This CAP introduces the concept of the "Fee Bump Transaction" (BFE) to allow paying transaction fees by arbitrary account.
There are a few cases that require charging transaction fees to the
account balance other than source account:
-
Games, exchanges, and other applications willing to pay user fees.
-
Pre-signed transactions in case of the
base_feeincrease. -
Coordinated multi-sig transactions with insufficient fees.
-
Fee optimization services that can ensure transactions execution in fee racing conditions.
This CAP introduces the way for paying fees for any transaction by extending TransactionEnvelope XDR.
The problem of paying fees for another account has been discussed multiple times. Current solutions are based on multi-sig with source account replacement or direct fees "refund" for user accounts. However, both approaches are not ideal and require multi-sig coordination. With the proposed transaction envelope extension users can sign and submit transactions to the application server endpoint which automatically modifies the envelope to allow paying fees from another account.
In the case of base_fee protocol parameter increase, long-lived
preauthorized transactions may become invalid because of insufficient
fees. Currently, we have no mechanism that would allow changing fees
for such transactions, or "pushing" them to the ledger in any other
way.
A Fee Bump Transaction actually consists of two separate transactions, an inner and outer transaction. We specify the XDR for these transactions followed by the semantics.
Using the proposed Future-upgradable
TransactionEnvelope feature, this CAP introduces a new
kind of transaction called FeeBump:
enum EnvelopeType
{
ENVELOPE_TYPE_TX0 = 0,
ENVELOPE_TYPE_SCP = 1,
ENVELOPE_TYPE_TX = 2,
ENVELOPE_TYPE_AUTH = 3,
ENVELOPE_TYPE_FEE_BUMP = 4 // new
};
struct FeeBumpTransaction {
AccountID feeSource;
uint64 fee;
TimeBounds *timeBounds;
union switch (EnvelopeType type) {
case ENVELOPE_TYPE_TX:
struct {
Transaction tx;
/* Each decorated signature is a signature over the SHA256 hash of
* a TransactionSignaturePayload */
DecoratedSignature signatures<20>;
} v1;
} innerTx;
}
struct TransactionSignaturePayload
{
Hash networkId;
union switch (EnvelopeType type)
{
case ENVELOPE_TYPE_TX:
Transaction tx;
case ENVELOPE_TYPE_FEE_BUMP:
Transaction4 feeBumpTx;
}
taggedTransaction;
};
/* A TransactionEnvelope wraps a transaction with signatures. */
union TransactionEnvelope switch (EnvelopeType type) {
case ENVELOPE_TYPE_TX0:
struct {
Transaction0 tx;
/* Each decorated signature is a signature over the SHA256 hash of
* a TransactionSignaturePayload */
DecoratedSignature signatures<20>;
} v0;
case ENVELOPE_TYPE_TX:
struct {
Transaction tx;
/* Each decorated signature is a signature over the SHA256 hash of
* a TransactionSignaturePayload */
DecoratedSignature signatures<20>;
} v1;
case ENVELOPE_TYPE_FEE_BUMP:
struct {
FeeBumpTransaction tx;
/* Each decorated signature is a signature over the SHA256 hash of
* a TransactionSignaturePayload */
DecoratedSignature signatures<20>;
} feeBump;
};
To confirm the intention to pay fees for the specific transaction on
behalf of the sponsor account, signatures should contain signatures
satisfying the low_threshold of the feeSource account. These
signatures do not contribute to the signing weight of innerTx--only
innerTx.signatures contributes to the inner transaction. In other
words, innerTx must be valid on except for innerTx.tx.fee, which
no longer matters.
The fee charging logic for FeeBump transactions follows CAP-0005, with the following changes:
-
When computing the fee, the effective number of operations is one greater than the number of operations in
innerTx---effectively the fee bump counts as an additional operation. -
The
feefield ofinnerTxis completely ignored, and can even be 0. -
If
innerTxdoes have a sufficientfeefield, it is possible for bothinnerTxand the FeeBump transaction to be included in the transaction set. In that case, the network double-charges for the transaction, as both thesourceAccountand thefeeSourceend up paying. -
If multiple FeeBump transactions with different
feeSourcefields are submitted with the sameinnerTx, the network can include them in the transaction set and double-charge for the FeeBumps. -
The same
feeSourcemay not submit a duplicate FeeBump transaction for the sameinnerTxwithin 10 ledgers.
When one or more FeeBump transactions execute for a given innerTx,
the results always include one TransactionResultPair for the
innerTx (which will contain a feeCharged of 0), and one for each
FeeBumpTransaction that was charged a fee. A FeeBumpTransaction
has a new result type that is present regardless of whether the
underlying innerTx succeeded or failed.
enum TransactionResultCode
{
txFEE_BUMPED = 1, // a fee bump transaction executed
// ... rest as is
};
struct FeeBumpResult {
Hash innerTxHash;
TransactionResultCode innerTxCode;
};
struct TransactionResult
{
int64 feeCharged; // actual fee charged for the transaction
union switch (TransactionResultCode code)
{
case txSUCCESS:
case txFAILED:
OperationResult results<>;
case txFEE_BUMPED:
FeeBumpResult feeBumpResult;
default:
void;
}
result;
// reserved for future use
union switch (int v)
{
case 0:
void;
}
ext;
};
The proposed solution requires minimum protocol changes and covers a wide range of potential use-cases leaving a room for the possible future modifications. It also resolves the problem of long-lived preauthorized transactions.
Transaction envelops with bump fee extension follow the standard lifecycle, do not require new operations or fundamental changes in ledger entities. Fees for any transaction envelope can be set at any time and resubmitted more than once, either from different accounts or after 10 ledgers have elapsed.
The transaction fee can be updated implicitly, without any interaction
with a user, which provides a consistent user experience. At the same
time, the feeSource account retains full control and is able to
refuse sponsoring fees in any particular situation.