Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
213 lines (169 sloc) 5.2 KB

Preamble

CAP: 0010
Title: Fee Bump Account
Author: Jeremy Rubin
Status: Draft
Created: 2018-11-13
Discussion: None
Protocol version: TBD

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-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.

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.