#Simple Summary
Transactions get stuck because of insufficient fee sometimes, especially if the protocol changes!
We partially address this with fee-bumping account.
#Abstract
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.
#Motivation
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-0005 helps with this issue a bit, but doesn't fully address the issue for when things are truly stuck because they are now invalid.
#Specification
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.
#Rationale
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.
#Backwards Compatibility
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.
#Implementation
None yet.