From f588678c02d17dae5c39a5767beacfb1542535ce Mon Sep 17 00:00:00 2001 From: Jon Jove Date: Wed, 22 Jan 2020 14:59:00 -0600 Subject: [PATCH] Introduce boolean expression trees to CAP-0023 --- core/cap-0023.md | 235 +++++++++++++++++++++++++---------------------- 1 file changed, 124 insertions(+), 111 deletions(-) diff --git a/core/cap-0023.md b/core/cap-0023.md index c21b855e3..e7c8211cc 100644 --- a/core/cap-0023.md +++ b/core/cap-0023.md @@ -66,33 +66,44 @@ enum LedgerEntryType enum ClaimPredicateType { CLAIM_PREDICATE_UNCONDITIONAL = 0, - CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME = 1, - CLAIM_PREDICATE_AFTER_ABSOLUTE_TIME = 2, - CLAIM_PREDICATE_BEFORE_RELATIVE_TIME = 3, - CLAIM_PREDICATE_AFTER_RELATIVE_TIME = 4 + CLAIM_PREDICATE_AND = 1, + CLAIM_PREDICATE_OR = 2, + CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME = 3, + CLAIM_PREDICATE_AFTER_ABSOLUTE_TIME = 4, + CLAIM_PREDICATE_BEFORE_RELATIVE_TIME = 5, + CLAIM_PREDICATE_AFTER_RELATIVE_TIME = 6 }; union ClaimPredicate switch (ClaimPredicateType type) { case CLAIM_PREDICATE_UNCONDITIONAL: void; +case CLAIM_PREDICATE_AND: + ClaimPredicate andPredicates<2>; +case CLAIM_PREDICATE_OR: + ClaimPredicate orPredicates<2>; case CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME: - Timepoint absBefore; + int64 absBefore; case CLAIM_PREDICATE_AFTER_ABSOLUTE_TIME: - Timepoint absAfter; + int64 absAfter; case CLAIM_PREDICATE_BEFORE_RELATIVE_TIME: - uint64 relBefore; // Seconds since closeTime of the ledger in which the - // ClaimableBalanceEntry was created + int64 relBefore; // Seconds since closeTime of the ledger in which the + // ClaimableBalanceEntry was created case CLAIM_PREDICATE_AFTER_RELATIVE_TIME: - uint64 relAfter; // Seconds since closeTime of the ledger in which the - // ClaimableBalanceEntry was created + int64 relAfter; // Seconds since closeTime of the ledger in which the + // ClaimableBalanceEntry was created }; struct ClaimCondition { AccountID destination; // The account that can use this condition - uint32 weight; // Cannot exceed 255, analogous to signer weight - ClaimPredicate predicate; // Contributes weight if predicate is satisfied + ClaimPredicate predicate; // Claimable if predicate is true +}; + +struct Claimant +{ + ClaimCondition condition; // Claimable if-and-only-if condition is satisfied + uint32 preauthorize; // Preauthorization flags for destination }; enum ClaimableBalanceIDType @@ -111,12 +122,8 @@ struct ClaimableBalanceEntry // Unique identifier for this ClaimableBalanceEntry ClaimableBalanceID balanceID; - // List of weighted conditions for destination accounts to claim - ClaimCondition condition<10>; - - // Total weight of satisfied conditions for destination account to claim - // Cannot exceed 255 - uint32 threshold; + // List of claimants with associated predicate and preauthorization + Claimant claimants<10>; // Amount of native asset to pay the reserve as of the ledger when this // ClaimableBalanceEntry was created @@ -128,10 +135,6 @@ struct ClaimableBalanceEntry // Amount of asset int64 amount; - // Set the authorization level for destination account trust line to - // authorize if it is less than authorize - uint32 authorize; - // reserved for future use union switch (int v) { @@ -164,7 +167,7 @@ struct LedgerEntry ``` Second, we introduce the new operations `CreateClaimableBalanceOp`, -`ClaimClaimableBalanceOp`, and `AuthorizeClaimableBalanceOp` as well as the +`ClaimClaimableBalanceOp`, and `PreauthorizeClaimableBalanceOp` as well as the corresponding changes to `OperationType` and `Operation`. ```c++ enum OperationType @@ -173,15 +176,14 @@ enum OperationType PATH_PAYMENT_STRICT_SEND = 13, CREATE_CLAIMABLE_BALANCE = 14, CLAIM_CLAIMABLE_BALANCE = 15, - AUTHORIZE_CLAIMABLE_BALANCE = 16 + PREAUTHORIZE_CLAIMABLE_BALANCE = 16 }; struct CreateClaimableBalanceOp { Asset asset; int64 amount; - ClaimCondition condition<10>; - uint32 threshold; + ClaimCondition conditions<10>; }; struct ClaimClaimableBalanceOp @@ -189,10 +191,11 @@ struct ClaimClaimableBalanceOp Hash balanceID; }; -struct AuthorizeClaimableBalanceOp +struct PreauthorizeClaimableBalanceOp { Hash balanceID; - TrustLineFlags authorize; + AccountID destination; + uint32 preauthorize; }; struct Operation @@ -209,15 +212,15 @@ struct Operation CreateClaimableBalanceOp createClaimableBalanceOp; case CLAIM_CLAIMABLE_BALANCE: ClaimClaimableBalanceOp claimClaimableBalanceOp; - case AUTHORIZE_CLAIMABLE_BALANCE: - AuthorizeClaimableBalanceOp authorizeClaimableBalanceOp; + case PREAUTHORIZE_CLAIMABLE_BALANCE: + PreuthorizeClaimableBalanceOp preauthorizeClaimableBalanceOp; } body; }; ``` Third, we introduce the result types `CreateClaimableBalanceResult`, -`ClaimClaimableBalanceResult`, and `AuthorizeClaimableBalanceResult` as well as +`ClaimClaimableBalanceResult`, and `PreauthorizeClaimableBalanceResult` as well as the corresponding changes to `OperationResult`. ```c++ enum CreateClaimableBalanceResultCode @@ -255,18 +258,20 @@ default: void; }; -enum AuthorizeClaimableBalanceResultCode +enum PreauthorizeClaimableBalanceResultCode { - AUTHORIZE_CLAIMABLE_BALANCE_SUCCESS = 0, - AUTHORIZE_CLAIMABLE_BALANCE_DOES_NOT_EXIST = -1, - AUTHORIZE_CLAIMABLE_BALANCE_NOT_ISSUER = -2, - AUTHORIZE_CLAIMABLE_BALANCE_TRUST_NOT_REQUIRED = -3, - AUTHORIZE_CLAIMABLE_BALANCE_CANNOT_REVOKE = -4 + PREAUTHORIZE_CLAIMABLE_BALANCE_SUCCESS = 0, + PREAUTHORIZE_CLAIMABLE_BALANCE_MALFORMED = -1, + PREAUTHORIZE_CLAIMABLE_BALANCE_DOES_NOT_EXIST = -2, + PREAUTHORIZE_CLAIMABLE_BALANCE_NOT_ISSUER = -3, + PREAUTHORIZE_CLAIMABLE_BALANCE_TRUST_NOT_REQUIRED = -4, + PREAUTHORIZE_CLAIMABLE_BALANCE_DESTINATION_CANNOT_CLAIM = -5, + PREAUTHORIZE_CLAIMABLE_BALANCE_CANNOT_REVOKE = -6 }; -union AuthorizeClaimableBalanceResult switch (AuthorizeClaimableBalanceResultCode code) +union PreauthorizeClaimableBalanceResult switch (PreauthorizeClaimableBalanceResultCode code) { -case AUTHORIZE_CLAIMABLE_BALANCE_SUCCESS: +case PREAUTHORIZE_CLAIMABLE_BALANCE_SUCCESS: void; default: void; @@ -283,7 +288,7 @@ case opINNER: case CLAIM_CLAIMABLE_BALANCE: ClaimClaimableBalanceResult claimClaimableBalanceResult; case AUTHORIZE_CLAIMABLE_BALANCE: - AuthorizeClaimableBalanceResult authorizeClaimableBalanceResult; + PreauthorizeClaimableBalanceResult preauthorizeClaimableBalanceResult; } tr; default: @@ -300,15 +305,19 @@ operation. `CreateClaimableBalanceOp` is invalid with - `asset` is invalid - `amount` is non-positive -- `threshold` is not between 1 and 255 (inclusive) -- `weight` on any `condition` is not between 1 and 255 (inclusive) +- `conditions` has length 0 +- `conditions[i].destination = conditions[j].destination` (for any `i != j`) +- `conditions[i].predicate` has depth greater than 4 (for any `i`) +- `conditions[i].predicate` contains a predicate of type `CLAIM_PREDICATE_AND` + with `andPredicates.size() < 2` or `CLAIM_PREDICATE_OR` with + `orPredicates.size() < 2` (for any `i`) The behavior of `CreateClaimableBalanceOp` is as follows: 1. Fail with `CREATE_CLAIMABLE_BALANCE_LOW_RESERVE` if the `sourceAccount` does - not have at least `min(1, condition.size()-1) * baseReserve` available + not have at least `max(1, conditions.size()-1) * baseReserve` available balance of native asset (see CAP-0003) -2. Deduct `min(1, condition.size()-1) * baseReserve` of native asset from +2. Deduct `max(1, conditions.size()-1) * baseReserve` of native asset from `sourceAccount` 3. Fail with `CREATE_CLAIMABLE_BALANCE_NO_TRUST` if the `sourceAccount` does not have a trust line for `asset` @@ -322,18 +331,20 @@ The behavior of `CreateClaimableBalanceOp` is as follows: to the SHA256 hash of the concatenation of the `sourceAccount` of the transaction, the `seqNum` of the transaction, and the index of this operation in the transaction - - `condition` as specified in the operation, with the exception that - - `CLAIM_PREDICATE_BEFORE_RELATIVE_TIME` will be converted to - `CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME` by adding `relBefore` to the - `closeTime` in the `LedgerHeader` - - `CLAIM_PREDICATE_BEFORE_RELATIVE_TIME` will be converted to - `CLAIM_PREDICATE_AFTER_ABSOLUTE_TIME` by adding `relAfter` to the - `closeTime` in the `LedgerHeader` - - `threshold` as specified in the operation - - `reserve` equal to `min(1, condition.size()-1) * baseReserve` + - For `i` between 0 and `conditions.size()-1` inclusive: + - `claimants[i].condition = conditions[i]`, with the exception that + - `CLAIM_PREDICATE_BEFORE_RELATIVE_TIME` will be converted to + `CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME` by adding `relBefore` to + the `closeTime` in the `LedgerHeader`. If this addition exceeds + `INT64_MAX` then use `INT64_MAX`. + - `CLAIM_PREDICATE_BEFORE_RELATIVE_TIME` will be converted to + `CLAIM_PREDICATE_AFTER_ABSOLUTE_TIME` by adding `relAfter` to the + `closeTime` in the `LedgerHeader`. If this addition exceeds + `INT64_MAX` then use `INT64_MAX`. + - `claimants[i].preauthorize = 0` + - `reserve` equal to `max(1, conditions.size()-1) * baseReserve` - `asset` as specified in the operation - `amount` as specified in the operation - - `authorize` equal to 0 8. Succeed with `CREATE_CLAIMABLE_BALANCE_SUCCESS` `CreateClaimableBalanceOp` requires medium threshold because it can be used to @@ -341,60 +352,68 @@ send funds. ### ClaimClaimableBalanceOp A `ClaimableBalanceEntry` can only be deleted by the `ClaimClaimableBalanceOp` -operation. `ClaimClaimableBalanceOp` cannot be malformed. The behavior of +operation. `ClaimClaimableBalanceOp` cannot be invalid. The behavior of `ClaimClaimableBalanceOp` is as follows: 1. Fail with `CLAIM_CLAIMABLE_BALANCE_DOES_NOT_EXIST` if there is no `ClaimableBalanceEntry` matching `balanceID`. -2. Calculate the total weight of `condition` with `destination = sourceAccount` - and satisfied predicates. -3. Fail with `CLAIM_CLAIMABLE_BALANCE_CANNOT_CLAIM` if the total weight is less - than `threshold`. -4. Fail with `CLAIM_CLAIMABLE_BALANCE_LINE_FULL` if the `sourceAccount` does not +2. Fail with `CLAIM_CLAIMABLE_BALANCE_CANNOT_CLAIM` if there is no `i` such that + `claimants[i].destination = sourceAccount` or if `claimants[i].predicate` + is not satisfied +3. Fail with `CLAIM_CLAIMABLE_BALANCE_LINE_FULL` if the `sourceAccount` does not have at least `reserve` available limit of native asset (see CAP-0003) -5. Add `reserve` of native asset to the `sourceAccount` -6. Skip to step 14 if `asset` is native -7. Skip to step 16 if `sourceAccount` is the issuer for `asset` -8. Skip to step 12 if `sourceAccount` has a trust line for `asset` -9. Fail with `opTOO_MANY_SUBENTRIES` if `sourceAccount` is already at the +4. Add `reserve` of native asset to the `sourceAccount` +5. Skip to step 13 if `asset` is native +6. Skip to step 15 if `sourceAccount` is the issuer for `asset` +7. Skip to step 11 if `sourceAccount` has a trust line for `asset` +8. Fail with `opTOO_MANY_SUBENTRIES` if `sourceAccount` is already at the subentry limit -10. Fail with `CLAIM_CLAIMABLE_BALANCE_LOW_RESERVE` if the `sourceAccount` does - not have sufficient available balance (see CAP-0003) to create a trust line -11. Create a trust line with the following properties: +9. Fail with `CLAIM_CLAIMABLE_BALANCE_LOW_RESERVE` if the `sourceAccount` does + not have sufficient available balance of native asset (see CAP-0003) to + create a trust line +10. Create a trust line with the following properties: - `sourceAccount` as specified in the operation - `asset` as specified in the `ClaimableBalanceEntry` - `balance = 0` - `limit = INT64_MAX` - - `flags = 0` -12. Set `flags = authorize` if `flags < authorize` -13. Fail with `CLAIM_CLAIMABLE_BALANCE_NOT_AUTHORIZED` if `flags = 0` or - `flags = AUTHORIZED_TO_MAINTAIN_LIABILITIES` and `authorize = 0` -14. Fail with `CLAIM_CLAIMABLE_BALANCE_LINE_FULL` if the `sourceAccount` does + - `flags = AUTHORIZED_FLAG` if the `issuer` of `asset` does not exist or if + `issuer` does not have `AUTH_REQUIRED` set, and `flags = 0` otherwise +11. If `claimants[i].preauthorize | AUTHORIZED_FLAG` then set + `flags = (flags | AUTHORIZED_FLAG) & ~AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG` +12. Fail with `CLAIM_CLAIMABLE_BALANCE_NOT_AUTHORIZED` if + `!(flags | AUTHORIZED_FLAG)` +13. Fail with `CLAIM_CLAIMABLE_BALANCE_LINE_FULL` if the `sourceAccount` does not have at least `amount` available limit of `asset` -15. Add `amount` of `asset` to the `sourceAccount` -16. Delete the `ClaimableBalanceEntry` -17. Succeed with `CLAIM_CLAIMABLE_BALANCE_SUCCESS` +14. Add `amount` of `asset` to the `sourceAccount` +15. Delete the `ClaimableBalanceEntry` +16. Succeed with `CLAIM_CLAIMABLE_BALANCE_SUCCESS` `ClaimClaimableBalanceOp` requires medium threshold because it can be used to create a trust line. -### AuthorizeClaimableBalanceOp -The `authorize` flag of a `ClaimableBalanceEntry` can be set with -`AuthorizeClaimableBalanceOp`. `AuthorizeClaimableBalanceOp` cannot be -malformed. The behavior of `AuthorizeClaimableBalanceOp` is as follows: +### PreauthorizeClaimableBalanceOp +The `preauthorize` flags of a `ClaimableBalanceEntry` can be set with +`PreauthorizeClaimableBalanceOp`. `PreauthorizeClaimableBalanceOp` is invalid +with `PREAUTHORIZE_CLAIMABLE_BALANCE_MALFORMED` if +`preauthorize & ~AUTHORIZED_FLAG`. -1. Fail with `AUTHORIZE_CLAIMABLE_BALANCE_DOES_NOT_EXIST` if there is no +The behavior of `PreauthorizeClaimableBalanceOp` is as follows: + +1. Fail with `PREAUTHORIZE_CLAIMABLE_BALANCE_DOES_NOT_EXIST` if there is no `ClaimableBalanceEntry` matching `balanceID`. -2. Fail with `AUTHORIZE_CLAIMABLE_BALANCE_NOT_ISSUER` if the `sourceAccount` is - not the issuer of `asset` -3. Fail with `AUTHORIZE_CLAIMABLE_BALANCE_TRUST_NOT_REQUIRED` if the `asset` is - not `AUTH_REQUIRED` -4. Fail with `AUTHORIZE_CLAIMABLE_BALANCE_CANNOT_REVOKE` if the `asset` is not - `AUTH_REVOCABLE` and `authorize < ClaimableBalanceEntry.authorize` -5. Set `authorize` as specified in the operation -6. Succeed with `AUTHORIZE_CLAIMABLE_BALANCE_SUCCESS` - -`AuthorizeClaimableBalanceOp` requires low threshold because it can only be used +2. Fail with `PREAUTHORIZE_CLAIMABLE_BALANCE_NOT_ISSUER` if the `sourceAccount` + is not the issuer of `asset` +3. Fail with `PREAUTHORIZE_CLAIMABLE_BALANCE_TRUST_NOT_REQUIRED` if + `sourceAccount` does not have `AUTH_REQUIRED` set +4. Fail with `PREAUTHORIZE_CLAIMABLE_BALANCE_DESTINATION_CANNOT_CLAIM` if there + is no `i` such that `claimants[i].destination = destination` +4. Fail with `PREAUTHORIZE_CLAIMABLE_BALANCE_CANNOT_REVOKE` if `sourceAccount` + does not have `AUTH_REVOCABLE` set and `preauthorize = 0` and + `claimants[i].preauthorize & AUTHORIZED_FLAG` +5. Set `claimants[i].preauthorize = preauthorize` +6. Succeed with `PREAUTHORIZE_CLAIMABLE_BALANCE_SUCCESS` + +`PreauthorizeClaimableBalanceOp` requires low threshold because it can only be used to authorize a trust line. ## Design Rationale @@ -430,15 +449,15 @@ does simplify the behavior of `ClaimableBalanceEntry` by guaranteeing that the receiving account always receives both the reserve and the amount of asset. ### ClaimableBalanceEntry claimants are accounts -For each `ClaimableBalanceEntry`, `condition` contains a finite and immutable +For each `ClaimableBalanceEntry`, `claimants` contains a finite and immutable list of accounts that could potentially claim the `ClaimableBalanceEntry`. Even if the conditions are satisfiable (which is not guaranteed), it is still possible for the `ClaimableBalanceEntry` to become stranded. If all of the -accounts listed in `condition` are merged and none of the private keys are +accounts listed in `claimants` are merged and none of the private keys are known, then the `ClaimableBalanceEntry` will no longer be claimable. Suppose that we try to relax this requirement in order to avoid this downside. -We could instead make `condition` contain a finite and immutable list of public +We could instead make `claimants` contain a finite and immutable list of public keys. The operation to claim the `ClaimableBalanceEntry` could then contain a signature over the tuple `(sourceAccount, balanceID)`. If the signature was not from one of the public keys that satisfy the conditions, then the operation @@ -463,18 +482,15 @@ issuer cannot send the funds without the existence of an authorized trust line. This results in additional communication, either off-chain or on-chain. This proposal seeks to simplify the situation. When a `ClaimableBalanceEntry` is claimed, a trust line for the asset will be implicitly created if one does not -exist. If the `authorize` flag is also set, then the trust line will also be -authorized regardless of whether it was implicitly created. - -Because the flag can only be set by the issuer and the `ClaimableBalanceEntry` -can only be claimed by accounts in `condition`, this allows the issuer to -effectively "pre-authorize". If there are multiple potential claimants and the -issuer does want to pre-authorize all of those accounts, they cannot use this -feature. But this is most useful for asset issuance, in which case there is -likely to be only one account. In any case, if the issuer is using this -functionality then they must be cognizant about clearing the `authorize` flag -for any `ClaimableBalanceEntry` claimable by an account from which they want to -revoke authorization. +exist. If `preauthorize` for the destination account has flags set, then the +trust line will also be authorized regardless of whether it was implicitly +created. + +Because the flag can only be set by the issuer, this allows the issuer to +effectively "pre-authorize" specific accounts. If the issuer is using this +functionality, then they must be cognizant that revoking trust from account `A` +will also require clearing the `preauthorize` flag for `A` on every +`ClaimableBalanceEntry` for which `A` is a claimant. ### Should it be possible to increase the amount of a ClaimableBalanceEntry? One issue which has been discussed during the development of this proposal is @@ -513,19 +529,16 @@ body: createClaimableBalanceOp: asset: X amount: - condition[0]: + conditions[0]: destination: B - weight: 1 predicate: type: CLAIM_PREDICATE_BEFORE_ABSOLUTE_TIME absBefore: T - condition[1]: + conditions[1]: destination: A - weight: 1 predicate: type: CLAIM_PREDICATE_AFTER_ABSOLUTE_TIME absAfter: T - threshold: 1 ``` ## Backwards Incompatibilities