CAP: 0010
Title: Fee Bump Account
Author: Jeremy Rubin
Status: Draft
Created: 2018-11-13
Discussion: None
Protocol version: TBD
Transactions get stuck because of insufficient fee sometimes, especially if the protocol changes!
We partially address this with fee-bumping account.
Transactions get stuck either because of insufficient fees being paid or because min fees increase.
CAP-10 specifies a 64 bit fee-only account (denominated in basefees) which can be used to bump subsequent transactions from the account.
In protocols which transactions are presigned or preauth for long durations, it's a major headache if they can't get in because of fee insuficiency. CAP-05 helps with this issue a bit, but doesn't fully address the issue for when things are truly stuck because they are now invalid.
We extend the Account with a fee_balance
account. fee_balance
is a receive
only account.
struct AccountEntry
{
// ... truncated
// reserved for future use
union switch (int v)
{
case 0:
void;
case 1:
struct
{
Liabilities liabilities;
union switch (int v)
{
case 0:
void;
case 1:
struct
{
int64 fee_balance;
union switch (int v)
{
case 0:
void;
} ext;
} v2;
}
ext;
} v1;
}
ext;
};
Transactions should not be rejected for insufficient fee before querying to see
if a fee_balance
exists and might cover the transaction.
When a transaction executes for a given source account, it bids a fee of fee
.
In this case, the fee is first charged to the account and then any excess to the
fee_balance
.
If the fee
is insufficient for a given ledger, it then bids a fee of fee + fee_balance
. In this case, an amount fee
paid is first charged against the
account, and then any excess is charged against the fee_balance
.
On merge, the fees are forwarded to the fee_balance
of the merge target.
We also introduce a new operation to add funds to a fee_balance
account.
enum OperationType
{
CREATE_ACCOUNT = 0,
PAYMENT = 1,
PATH_PAYMENT = 2,
MANAGE_OFFER = 3,
CREATE_PASSIVE_OFFER = 4,
SET_OPTIONS = 5,
CHANGE_TRUST = 6,
ALLOW_TRUST = 7,
ACCOUNT_MERGE = 8,
INFLATION = 9,
MANAGE_DATA = 10,
BUMP_SEQUENCE = 11,
BUMP_FEE = 12
};
struct BumpFeeOp
{
AccountID recipient;
SequenceNumber unless_passed;
int64 amount;
};
enum BumpFeeResultCode
{
// codes considered as "success" for the operation
BUMP_FEE_SUCCESS = 0,
BUMP_FEE_INSUFFICIENT_BALANCE = 1,
};
union BumpSequenceResult switch (BumpSequenceResultCode code)
{
case BUMP_FEE_SUCCESS:
void;
default:
void;
};
union OperationResult switch (OperationResultCode code)
{
case opINNER:
union switch (OperationType type)
{
case CREATE_ACCOUNT:
CreateAccountResult createAccountResult;
case PAYMENT:
PaymentResult paymentResult;
case PATH_PAYMENT:
PathPaymentResult pathPaymentResult;
case MANAGE_OFFER:
ManageOfferResult manageOfferResult;
case CREATE_PASSIVE_OFFER:
ManageOfferResult createPassiveOfferResult;
case SET_OPTIONS:
SetOptionsResult setOptionsResult;
case CHANGE_TRUST:
ChangeTrustResult changeTrustResult;
case ALLOW_TRUST:
AllowTrustResult allowTrustResult;
case ACCOUNT_MERGE:
AccountMergeResult accountMergeResult;
case INFLATION:
InflationResult inflationResult;
case MANAGE_DATA:
ManageDataResult manageDataResult;
case BUMP_SEQUENCE:
BumpSequenceResult bumpSeqResult;
case BUMP_FEE:
BumpFeeResult bumpFeeResult;
}
tr;
default:
void;
};
The semantics of which are as follows:
If the account does not exist, nothing happens.
If the account sequence number is greater than unless_passed
, nothing happens.
Otherwise, min(op.amount, op.source.balance - op.source.reserved)
is deducted
from the source account and added to the destination's fee_balance
.
It's not too expensive to add new entries to accounts, so we add a full 64 bits.
Alternatively, we could store a separate table for accounts with a
fee_balance
as a subentry, but this is an implementer's choice.
We don't allow to recover the funds from the fee_balance
, but we do allow them
to transfer during merge. This is a sort of "fee monad", which prevents
malapropriation of fee subsidies paid by third parties.
We asess fees against the account's balance first because the fee_balance
should only be touched in cases where the balance was insufficient to cover the
expressed transaction fee, or the expressed transaction fee was insufficient for
inclusion.
Transactions which don't pay base fee rate must now query to see if a fee_balance exists rather than being outright invalid.
Previously, a user might expect that transactions signed with 0 fee would not be valid. Under this proposal, they may be. This doesn't really change the status quo because users are not given a guarantee about transaction fees never decreasing.
None yet.